org.jgroups.stack.DiagnosticsHandler Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
package org.jgroups.stack;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.Property;
import org.jgroups.blocks.cs.ReceiverAdapter;
import org.jgroups.conf.PropertyConverters;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.*;
import java.io.*;
import java.net.*;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* @author Bela Ban
* @since 3.0
*/
public class DiagnosticsHandler extends ReceiverAdapter implements Closeable {
public static final String UDP_THREAD_NAME="UdpDiagHandler";
public static final String TCP_THREAD_NAME="TcpDiagHandler";
@Property(description="Switch to enable diagnostic probing")
protected boolean enabled=true;
@Property(description="Use a multicast socket to listen for probe requests (ignored if diag.enabled is false)")
protected volatile boolean enable_udp=true;
@Property(description="Use a TCP socket to listen for probe requests (ignored if diag.enabled is false)")
protected volatile boolean enable_tcp;
@Property(description="Multicast address for diagnostic probing (UDP MulticastSocket). Used when enable_udp " +
"is true", defaultValueIPv4="224.0.75.75",defaultValueIPv6="ff0e::0:75:75")
protected InetAddress mcast_addr;
@Property(description="Port for diagnostic probing. Default is 7500")
protected int port=7500;
@Property(description="Bind address for diagnostic probing, to bind the TCP socket. Used when enable_tcp is true")
protected InetAddress bind_addr;
@Property(description="The number of ports to be probed for an available port (TCP)")
protected int port_range=50;
@Property(description="TTL of the diagnostics multicast socket")
protected int ttl=8;
@Property(converter=PropertyConverters.NetworkInterfaceList.class,
description="Comma delimited list of interfaces (IP addrs or interface names) that the multicast socket should bind to")
protected List bind_interfaces;
@Property(description="Authorization passcode for diagnostics. If specified every probe query will be authorized")
protected String passcode;
protected ServerSocket srv_sock; // when TCP is used
protected Runner udp_runner, tcp_runner;
protected MulticastSocket udp_mcast_sock; // receiving of mcast packets when UDP is used
protected DatagramSocket udp_ucast_sock; // sending of UDP responses
protected final Set handlers=new CopyOnWriteArraySet<>();
protected final Log log;
protected final SocketFactory socket_factory;
protected final ThreadFactory thread_factory;
protected Function print_headers=b -> "";
protected Function same_cluster=b -> true;
protected final BiConsumer udp_response_sender=
(sender,response) -> sendResponse(udp_ucast_sock, sender, response.getBytes());
public DiagnosticsHandler printHeaders(Function f) {
print_headers=Objects.requireNonNull(f);
return this;
}
public DiagnosticsHandler sameCluster(Function f) {
same_cluster=Objects.requireNonNull(f);
return this;
}
/** Constructor used for standalone apps (without a JGroups stack) */
public DiagnosticsHandler() throws Exception {
this(LogFactory.getLog(DiagnosticsHandler.class), new DefaultSocketFactory(),
new DefaultThreadFactory("diag", true, true));
Configurator.setDefaultAddressValues(this, Util.getIpStackType());
}
public DiagnosticsHandler(Log log, SocketFactory socket_factory, ThreadFactory thread_factory) {
this.log=log;
this.socket_factory=socket_factory;
this.thread_factory=thread_factory;
}
public boolean isEnabled() {return enabled;}
public DiagnosticsHandler setEnabled(boolean f) {enabled=f; return this;}
public DiagnosticsHandler setMcastAddress(InetAddress a) {this.mcast_addr=a; return this;}
public InetAddress getMcastAddress() {return mcast_addr;}
public DiagnosticsHandler setBindAddress(InetAddress a) {this.bind_addr=a; return this;}
public InetAddress getBindAddress() {return bind_addr;}
public boolean udpEnabled() {return enable_udp;}
public DiagnosticsHandler enableUdp(boolean f) {enable_udp=f; if(f) enabled=f; return this;}
public boolean tcpEnabled() {return enable_tcp;}
public DiagnosticsHandler enableTcp(boolean f) {enable_tcp=f; if(f) enabled=f; return this;}
public int getPort() {return port;}
public DiagnosticsHandler setPort(int p) {port=p; return this;}
public int getPortRange() {return port_range;}
public DiagnosticsHandler setPortRange(int r) {port_range=r; return this;}
public int getTtl() {return ttl;}
public DiagnosticsHandler setTtl(int d) {this.ttl=d; return this;}
public String getPasscode() {return passcode;}
public DiagnosticsHandler setPasscode(String d) {this.passcode=d; return this;}
public List getBindInterfaces() {return bind_interfaces;}
public DiagnosticsHandler setBindInterfaces(List l) {this.bind_interfaces=l; return this;}
public DiagnosticsHandler setThreadNames() {
if(udp_runner != null) {
Thread tmp=udp_runner.getThread();
if(tmp != null)
thread_factory.renameThread(UDP_THREAD_NAME, tmp);
}
if(tcp_runner != null && tcp_runner.isRunning()) {
Thread tmp=tcp_runner.getThread();
if(tmp != null)
thread_factory.renameThread(TCP_THREAD_NAME, tmp);
}
return this;
}
public DiagnosticsHandler unsetThreadNames() {
if(udp_runner != null)
udp_runner.threadName(UDP_THREAD_NAME);
if(tcp_runner != null)
tcp_runner.threadName(TCP_THREAD_NAME);
return this;
}
public Set getProbeHandlers() {return handlers;}
public DiagnosticsHandler registerProbeHandler(ProbeHandler handler) {
handlers.add(Objects.requireNonNull(handler));
return this;
}
public DiagnosticsHandler unregisterProbeHandler(ProbeHandler handler) {
if(handler != null)
handlers.remove(handler);
return this;
}
public DiagnosticsHandler start() throws Exception {
if(!enabled)
return this;
if(!enable_udp && !enable_tcp)
throw new IllegalStateException("both UDP and TCP are disabled - enable at least 1 of them");
if(enable_udp)
startUDP();
if(enable_tcp)
startTCP();
return this;
}
public void close() throws IOException {
stop();
}
public DiagnosticsHandler stop() {
Util.close(udp_runner, tcp_runner);
return this; // only stops if running
}
@ManagedAttribute(description="Is the diagnostics handler running?")
public boolean isRunning() {
return udp_runner != null && udp_runner.isRunning() || tcp_runner != null && tcp_runner.isRunning();
}
/** Returns the local datagram socket address (UDP) or the srv address (TCP) */
public SocketAddress getLocalAddress() {
if(udp_ucast_sock != null) {
InetAddress addr=udp_ucast_sock.getLocalAddress();
if(addr == null || addr. isAnyLocalAddress()) {
try {
addr=InetAddress.getLocalHost();
}
catch(UnknownHostException e) {
}
}
return new InetSocketAddress(addr, udp_ucast_sock.getLocalPort());
}
if(srv_sock != null) {
InetAddress addr=srv_sock.getInetAddress();
if(addr == null || addr.isAnyLocalAddress()) {
try {
addr=InetAddress.getLocalHost();
}
catch(UnknownHostException e) {
}
}
return new InetSocketAddress(addr, srv_sock.getLocalPort());
}
return null;
}
protected void runUDP() {
byte[] buf=new byte[10000]; // requests are small (responses might be bigger)
DatagramPacket packet=new DatagramPacket(buf, 0, buf.length);
try {
udp_mcast_sock.receive(packet);
int payloadStartOffset = 0;
if(passcode != null)
payloadStartOffset=authorizeProbeRequest(packet);
handleDiagnosticProbe(packet.getSocketAddress(),
new String(packet.getData(), packet.getOffset() + payloadStartOffset, packet.getLength()),
udp_response_sender);
}
catch(IOException socket_ex) {
}
catch(Throwable e) {
log.error(Util.getMessage("FailureHandlingDiagnosticsRequest"), e);
}
}
protected void runTCP() {
SocketAddress sender=null;
try(Socket client_sock=srv_sock.accept();
InputStream input=client_sock.getInputStream();
OutputStream output=client_sock.getOutputStream();) {
sender=client_sock.getRemoteSocketAddress();
String request=Util.readLine(input);
handleDiagnosticProbe(sender, request, (snd,response) -> {
try {
output.write(response.getBytes());
}
catch(IOException e) {
log.error("failed handling TCP probe request: %s", e.getMessage());
}
});
}
catch(SocketException sock_ex) {
}
catch(Throwable t) {
log.error("failed processing TCP client request from %s: %s", sender, t);
}
}
protected DiagnosticsHandler startUDP() throws Exception {
if(udp_ucast_sock == null || udp_ucast_sock.isClosed())
udp_ucast_sock=socket_factory.createDatagramSocket("jgroups.tp.diag.udp_ucast_sock");
if(udp_mcast_sock == null || udp_mcast_sock.isClosed()) {
// https://issues.redhat.com/browse/JGRP-777 - this doesn't work on MacOS, and we don't have
// cross talking on Windows anyway, so we just do it for Linux. (How about Solaris ?)
// If possible, the MulticastSocket(SocketAddress) ctor is used which binds to diagnostics_addr:diagnostics_port.
// This acts like a filter, dropping multicasts to different multicast addresses
if(Util.can_bind_to_mcast_addr)
udp_mcast_sock=Util.createMulticastSocket(socket_factory, "jgroups.tp.diag.udp_mcast_sock",
mcast_addr, port, log);
else
udp_mcast_sock=socket_factory.createMulticastSocket("jgroups.tp.diag.udp_mcast_sock", port);
try {
udp_mcast_sock.setTimeToLive(ttl);
}
catch(Exception ex) {
log.error("failed setting TTL %d in MulticastSocket: %s", ttl, ex.getMessage());
}
List interfaces=bind_interfaces != null? bind_interfaces : Util.getAllAvailableInterfaces();
bindToInterfaces(interfaces, udp_mcast_sock);
}
if(udp_runner == null)
udp_runner=new Runner(thread_factory, UDP_THREAD_NAME, this::runUDP,
() -> Util.close(udp_mcast_sock, udp_ucast_sock)).daemon(true);
udp_runner.start();
return this;
}
protected DiagnosticsHandler startTCP() throws Exception {
if(srv_sock == null || srv_sock.isClosed())
srv_sock=Util.createServerSocket(socket_factory, "jgroups.tp.diag.tcp_sock",
bind_addr, port, port+ port_range, 0);
if(tcp_runner == null)
tcp_runner=new Runner(thread_factory, TCP_THREAD_NAME, this::runTCP, () -> Util.close(srv_sock));
tcp_runner.start();
return this;
}
protected void handleDiagnosticProbe(SocketAddress sender, String request,
BiConsumer rsp_sender) {
StringTokenizer tok=new StringTokenizer(request);
List list=new ArrayList<>(10);
while(tok.hasMoreTokens()) {
String req=tok.nextToken().trim();
if(!req.isEmpty()) {
// if -cluster=: discard requests that have a cluster name != our own cluster name
if(req.startsWith("cluster=")) {
if(!same_cluster.apply(req)) {
log.debug("probe response dropped as cluster %s does not match", req.substring("cluster=".length()));
return;
}
continue;
}
list.add(req);
}
}
if(list.isEmpty()) {
String default_rsp=print_headers.apply(true);
rsp_sender.accept(sender, default_rsp);
return;
}
String[] tokens=new String[list.size()];
for(int i=0; i < list.size(); i++)
tokens[i]=list.get(i);
Map map=new LinkedHashMap<>();
for(ProbeHandler handler: handlers) {
try {
Map m=handler.handleProbe(tokens);
if(m == null || m.isEmpty())
continue;
for(Map.Entry e: m.entrySet()) {
String key=e.getKey(), val=e.getValue();
String existing=map.putIfAbsent(key, val);
if(existing != null)
log.warn("%s already present; skipping addition of new key by probe handler %s", key, handler);
}
}
catch(IllegalArgumentException ex) {
log.warn(ex.getMessage());
return;
}
}
String tmp=print_headers.apply(false);
StringBuilder info=new StringBuilder(tmp);
for(Map.Entry entry: map.entrySet())
info.append(String.format("%s=%s\r\n", entry.getKey(), entry.getValue()));
String diag_rsp=info.toString();
rsp_sender.accept(sender, diag_rsp);
}
/**
* Performs authorization on given DatagramPacket.
* @param packet to authorize
* @return offset in DatagramPacket where request payload starts
* @throws Exception thrown if passcode received from client does not match set passcode
*/
protected int authorizeProbeRequest(DatagramPacket packet) throws Exception {
int offset = 0;
ByteArrayInputStream bis = new ByteArrayInputStream(packet.getData());
DataInputStream in = new DataInputStream(bis);
long t1 = in.readLong();
double q1 = in.readDouble();
int length = in.readInt();
byte[] digest = new byte[length];
in.readFully(digest);
offset = 8 + 8 + 4 + digest.length;
byte[] local = Util.createDigest(passcode, t1, q1);
if(!MessageDigest.isEqual(digest, local))
throw new Exception("Authorization failed! Make sure correct passcode is used");
return offset;
}
protected void sendResponse(DatagramSocket sock, SocketAddress sender, byte[] buf) {
try {
DatagramPacket p=new DatagramPacket(buf, 0, buf.length, sender);
sock.send(p);
}
catch(Throwable t) {
log.error(Util.getMessage("FailedSendingDiagRspTo") + sender, t);
}
}
protected void bindToInterfaces(List interfaces, MulticastSocket s) {
SocketAddress group_addr=new InetSocketAddress(mcast_addr, port);
for(Iterator it=interfaces.iterator(); it.hasNext();) {
NetworkInterface i=it.next();
try {
if(Util.isUp(i)) {
List inet_addrs=i.getInterfaceAddresses();
if(inet_addrs != null && !inet_addrs.isEmpty()) { // fix for VM crash - suggested by [email protected]
s.joinGroup(group_addr, i);
log.trace("joined %s on %s", group_addr, i.getName());
}
}
}
catch(Exception e) { // also catches NPE in getInterfaceAddresses() (https://issues.redhat.com/browse/JGRP-1845)
log.warn("failed to join " + group_addr + " on " + i.getName() + ": " + e);
}
}
}
public interface ProbeHandler {
/**
* Handles a probe. For each key that is handled, the key and its result should be in the returned map.
* @return Map. A map of keys and values. A null return value is permissible.
*/
Map handleProbe(String... keys);
/** Returns a list of supported keys */
String[] supportedKeys();
}
}