/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.activemq.artemis.core.protocol.openwire; import javax.jms.InvalidClientIDException; import javax.security.cert.X509Certificate; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import org.apache.activemq.advisory.AdvisorySupport; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQSecurityException; import org.apache.activemq.artemis.api.core.BaseInterceptor; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener; import org.apache.activemq.artemis.api.core.client.TopologyMember; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQConnectionContext; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQProducerBrokerExchange; import org.apache.activemq.artemis.core.protocol.openwire.amq.AMQSession; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.cluster.ClusterConnection; import org.apache.activemq.artemis.core.server.cluster.ClusterManager; import org.apache.activemq.artemis.reader.MessageUtil; import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Acceptor; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.utils.CertificateUtil; import org.apache.activemq.artemis.utils.DataConstants; import org.apache.activemq.command.ActiveMQMessage; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.command.BrokerId; import org.apache.activemq.command.BrokerInfo; import org.apache.activemq.command.Command; import org.apache.activemq.command.ConnectionControl; import org.apache.activemq.command.ConnectionInfo; import org.apache.activemq.command.ConsumerId; import org.apache.activemq.command.DestinationInfo; import org.apache.activemq.command.MessageDispatch; import org.apache.activemq.command.MessageId; import org.apache.activemq.command.ProducerId; import org.apache.activemq.command.ProducerInfo; import org.apache.activemq.command.WireFormatInfo; import org.apache.activemq.openwire.OpenWireFormat; import org.apache.activemq.openwire.OpenWireFormatFactory; import org.apache.activemq.state.ProducerState; import org.apache.activemq.util.IdGenerator; import org.apache.activemq.util.InetAddressUtil; import org.apache.activemq.util.LongSequenceGenerator; public class OpenWireProtocolManager implements ProtocolManager<Interceptor>, ClusterTopologyListener { private static final List<String> websocketRegistryNames = Collections.EMPTY_LIST; private static final IdGenerator BROKER_ID_GENERATOR = new IdGenerator(); private static final IdGenerator ID_GENERATOR = new IdGenerator(); private final LongSequenceGenerator messageIdGenerator = new LongSequenceGenerator(); private final ActiveMQServer server; private final OpenWireProtocolManagerFactory factory; private OpenWireFormatFactory wireFactory; private boolean prefixPacketSize = true; private BrokerId brokerId; protected final ProducerId advisoryProducerId = new ProducerId(); private final CopyOnWriteArrayList<OpenWireConnection> connections = new CopyOnWriteArrayList<>(); private final Map<String, AMQConnectionContext> clientIdSet = new HashMap<>(); private String brokerName; private final Map<String, TopologyMember> topologyMap = new ConcurrentHashMap<>(); private final LinkedList<TopologyMember> members = new LinkedList<>(); private final ScheduledExecutorService scheduledPool; //bean properties //http://activemq.apache.org/failover-transport-reference.html private boolean rebalanceClusterClients = false; private boolean updateClusterClients = false; private boolean updateClusterClientsOnRemove = false; //http://activemq.apache.org/activemq-inactivitymonitor.html private long maxInactivityDuration = 30 * 1000L; private long maxInactivityDurationInitalDelay = 10 * 1000L; private boolean useKeepAlive = true; private final OpenWireMessageConverter messageConverter; private final Map<SimpleString, RoutingType> prefixes = new HashMap<>(); public OpenWireProtocolManager(OpenWireProtocolManagerFactory factory, ActiveMQServer server) { this.factory = factory; this.server = server; this.wireFactory = new OpenWireFormatFactory(); // preferred prop, should be done via config wireFactory.setCacheEnabled(false); advisoryProducerId.setConnectionId(ID_GENERATOR.generateId()); scheduledPool = server.getScheduledPool(); this.messageConverter = new OpenWireMessageConverter(wireFactory.createWireFormat()); final ClusterManager clusterManager = this.server.getClusterManager(); ClusterConnection cc = clusterManager.getDefaultConnection(null); if (cc != null) { cc.addClusterTopologyListener(this); } } public OpenWireFormat getNewWireFormat() { return (OpenWireFormat) wireFactory.createWireFormat(); } @Override public void nodeUP(TopologyMember member, boolean last) { if (topologyMap.put(member.getNodeId(), member) == null) { updateClientClusterInfo(); } } @Override public void nodeDown(long eventUID, String nodeID) { if (topologyMap.remove(nodeID) != null) { updateClientClusterInfo(); } } public void removeConnection(ConnectionInfo info, Throwable error) throws InvalidClientIDException { synchronized (clientIdSet) { String clientId = info.getClientId(); if (clientId != null) { AMQConnectionContext context = this.clientIdSet.get(clientId); if (context != null && context.decRefCount() == 0) { //connection is still there and need to close context.getConnection().disconnect(error != null); this.connections.remove(context.getConnection()); this.clientIdSet.remove(clientId); } } else { throw new InvalidClientIDException("No clientID specified for connection disconnect request"); } } } public ScheduledExecutorService getScheduledPool() { return scheduledPool; } public ActiveMQServer getServer() { return server; } private void updateClientClusterInfo() { synchronized (members) { members.clear(); members.addAll(topologyMap.values()); } for (OpenWireConnection c : this.connections) { ConnectionControl control = newConnectionControl(); try { c.updateClient(control); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e); c.sendException(e); } } } @Override public boolean acceptsNoHandshake() { return false; } @Override public ProtocolManagerFactory<Interceptor> getFactory() { return factory; } @Override public void updateInterceptors(List<BaseInterceptor> incomingInterceptors, List<BaseInterceptor> outgoingInterceptors) { // NO-OP } @Override public ConnectionEntry createConnectionEntry(Acceptor acceptorUsed, Connection connection) { OpenWireFormat wf = (OpenWireFormat) wireFactory.createWireFormat(); OpenWireConnection owConn = new OpenWireConnection(connection, server, server.getExecutorFactory().getExecutor(), this, wf); owConn.sendHandshake(); //first we setup ttl to -1 //then when negotiation, we handle real ttl and delay ConnectionEntry entry = new ConnectionEntry(owConn, null, System.currentTimeMillis(), -1); owConn.setConnectionEntry(entry); return entry; } @Override public void removeHandler(String name) { } @Override public void handleBuffer(RemotingConnection connection, ActiveMQBuffer buffer) { } @Override public void addChannelHandlers(ChannelPipeline pipeline) { // each read will have a full packet with this pipeline.addLast("packet-decipher", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, DataConstants.SIZE_INT)); } @Override public boolean isProtocol(byte[] array) { if (array.length < 8) { throw new IllegalArgumentException("Protocol header length changed " + array.length); } int start = this.prefixPacketSize ? 4 : 0; int j = 0; // type if (array[start] != WireFormatInfo.DATA_STRUCTURE_TYPE) { return false; } start++; WireFormatInfo info = new WireFormatInfo(); final byte[] magic = info.getMagic(); int remainingLen = array.length - start; int useLen = remainingLen > magic.length ? magic.length : remainingLen; useLen += start; // magic for (int i = start; i < useLen; i++) { if (array[i] != magic[j]) { return false; } j++; } return true; } @Override public void handshake(NettyServerConnection connection, ActiveMQBuffer buffer) { } @Override public List<String> websocketSubprotocolIdentifiers() { return websocketRegistryNames; } public void addConnection(OpenWireConnection connection, ConnectionInfo info) throws Exception { String username = info.getUserName(); String password = info.getPassword(); try { validateUser(username, password, connection); } catch (ActiveMQSecurityException e) { // We need to send an exception used by the openwire SecurityException ex = new SecurityException("User name [" + username + "] or password is invalid."); ex.initCause(e); throw ex; } String clientId = info.getClientId(); if (clientId == null) { throw new InvalidClientIDException("No clientID specified for connection request"); } synchronized (clientIdSet) { AMQConnectionContext context; context = clientIdSet.get(clientId); if (context != null) { if (info.isFailoverReconnect()) { OpenWireConnection oldConnection = context.getConnection(); oldConnection.disconnect(true); connections.remove(oldConnection); connection.reconnect(context, info); } else { throw new InvalidClientIDException("Broker: " + getBrokerName() + " - Client: " + clientId + " already connected from " + context.getConnection().getRemoteAddress()); } } else { //new connection context = connection.initContext(info); clientIdSet.put(clientId, context); } connections.add(connection); ActiveMQTopic topic = AdvisorySupport.getConnectionAdvisoryTopic(); // do not distribute passwords in advisory messages. usernames okay ConnectionInfo copy = info.copy(); copy.setPassword(""); fireAdvisory(context, topic, copy); // init the conn context.getConnection().addSessions(context.getConnectionState().getSessionIds()); } } public void fireAdvisory(AMQConnectionContext context, ActiveMQTopic topic, Command copy) throws Exception { this.fireAdvisory(context, topic, copy, null, null); } public BrokerId getBrokerId() { // TODO: Use the Storage ID here... if (brokerId == null) { brokerId = new BrokerId(BROKER_ID_GENERATOR.generateId()); } return brokerId; } /* * See AdvisoryBroker.fireAdvisory() */ public void fireAdvisory(AMQConnectionContext context, ActiveMQTopic topic, Command command, ConsumerId targetConsumerId, String originalConnectionId) throws Exception { ActiveMQMessage advisoryMessage = new ActiveMQMessage(); if (originalConnectionId == null) { originalConnectionId = context.getConnectionId().getValue(); } advisoryMessage.setStringProperty(MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString(), originalConnectionId); advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_ORIGIN_BROKER_NAME, getBrokerName()); String id = getBrokerId() != null ? getBrokerId().getValue() : "NOT_SET"; advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_ORIGIN_BROKER_ID, id); String url = context.getConnection().getLocalAddress(); advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_ORIGIN_BROKER_URL, url); // set the data structure advisoryMessage.setDataStructure(command); advisoryMessage.setPersistent(false); advisoryMessage.setType(AdvisorySupport.ADIVSORY_MESSAGE_TYPE); advisoryMessage.setMessageId(new MessageId(advisoryProducerId, messageIdGenerator.getNextSequenceId())); advisoryMessage.setTargetConsumerId(targetConsumerId); advisoryMessage.setDestination(topic); advisoryMessage.setResponseRequired(false); advisoryMessage.setProducerId(advisoryProducerId); boolean originalFlowControl = context.isProducerFlowControl(); final AMQProducerBrokerExchange producerExchange = new AMQProducerBrokerExchange(); producerExchange.setConnectionContext(context); producerExchange.setProducerState(new ProducerState(new ProducerInfo())); try { context.setProducerFlowControl(false); AMQSession sess = context.getConnection().getAdvisorySession(); if (sess != null) { sess.send(producerExchange.getProducerState().getInfo(), advisoryMessage, false); } } finally { context.setProducerFlowControl(originalFlowControl); } } public String getBrokerName() { if (brokerName == null) { try { brokerName = InetAddressUtil.getLocalHostName().toLowerCase(Locale.ENGLISH); } catch (Exception e) { brokerName = server.getNodeID().toString(); } } return brokerName; } protected ConnectionControl newConnectionControl() { ConnectionControl control = new ConnectionControl(); String uri = generateMembersURI(rebalanceClusterClients); control.setConnectedBrokers(uri); control.setRebalanceConnection(rebalanceClusterClients); return control; } private String generateMembersURI(boolean flip) { String uri; StringBuffer connectedBrokers = new StringBuffer(); String separator = ""; synchronized (members) { if (members.size() > 0) { for (TopologyMember member : members) { connectedBrokers.append(separator).append(member.toURI()); separator = ","; } // The flip exists to guarantee even distribution of URIs when sent to the client // in case of failures you won't get all the connections failing to a single server. if (flip && members.size() > 1) { members.addLast(members.removeFirst()); } } } uri = connectedBrokers.toString(); return uri; } public boolean isFaultTolerantConfiguration() { return false; } public void postProcessDispatch(MessageDispatch md) { // TODO Auto-generated method stub } public boolean isStopped() { // TODO Auto-generated method stub return false; } public void preProcessDispatch(MessageDispatch messageDispatch) { // TODO Auto-generated method stub } public boolean isStopping() { return false; } public void validateUser(String login, String passcode, OpenWireConnection connection) throws Exception { X509Certificate[] certificates = null; if (connection.getTransportConnection() instanceof NettyConnection) { certificates = CertificateUtil.getCertsFromChannel(((NettyConnection) connection.getTransportConnection()).getChannel()); } server.getSecurityStore().authenticate(login, passcode, certificates); } public void sendBrokerInfo(OpenWireConnection connection) throws Exception { BrokerInfo brokerInfo = new BrokerInfo(); brokerInfo.setBrokerName(getBrokerName()); brokerInfo.setBrokerId(new BrokerId("" + server.getNodeID())); brokerInfo.setPeerBrokerInfos(null); brokerInfo.setFaultTolerantConfiguration(false); brokerInfo.setBrokerURL(connection.getLocalAddress()); //cluster support yet to support brokerInfo.setPeerBrokerInfos(null); connection.dispatch(brokerInfo); } public void setUpInactivityParams(OpenWireConnection connection, WireFormatInfo command) throws IOException { long inactivityDurationToUse = command.getMaxInactivityDuration() > this.maxInactivityDuration ? this.maxInactivityDuration : command.getMaxInactivityDuration(); long inactivityDurationInitialDelayToUse = command.getMaxInactivityDurationInitalDelay() > this.maxInactivityDurationInitalDelay ? this.maxInactivityDurationInitalDelay : command.getMaxInactivityDurationInitalDelay(); boolean useKeepAliveToUse = this.maxInactivityDuration == 0L ? false : this.useKeepAlive; connection.setUpTtl(inactivityDurationToUse, inactivityDurationInitialDelayToUse, useKeepAliveToUse); } /** * URI property */ @SuppressWarnings("unused") public void setRebalanceClusterClients(boolean rebalance) { this.rebalanceClusterClients = rebalance; } /** * URI property */ @SuppressWarnings("unused") public boolean isRebalanceClusterClients() { return this.rebalanceClusterClients; } /** * URI property */ @SuppressWarnings("unused") public void setUpdateClusterClients(boolean updateClusterClients) { this.updateClusterClients = updateClusterClients; } public boolean isUpdateClusterClients() { return this.updateClusterClients; } /** * URI property */ @SuppressWarnings("unused") public void setUpdateClusterClientsOnRemove(boolean updateClusterClientsOnRemove) { this.updateClusterClientsOnRemove = updateClusterClientsOnRemove; } /** * URI property */ @SuppressWarnings("unused") public boolean isUpdateClusterClientsOnRemove() { return this.updateClusterClientsOnRemove; } public void setBrokerName(String name) { this.brokerName = name; } public boolean isUseKeepAlive() { return useKeepAlive; } @SuppressWarnings("unused") public void setUseKeepAlive(boolean useKeepAlive) { this.useKeepAlive = useKeepAlive; } public long getMaxInactivityDuration() { return maxInactivityDuration; } public void setMaxInactivityDuration(long maxInactivityDuration) { this.maxInactivityDuration = maxInactivityDuration; } @SuppressWarnings("unused") public long getMaxInactivityDurationInitalDelay() { return maxInactivityDurationInitalDelay; } @SuppressWarnings("unused") public void setMaxInactivityDurationInitalDelay(long maxInactivityDurationInitalDelay) { this.maxInactivityDurationInitalDelay = maxInactivityDurationInitalDelay; } @Override public void setAnycastPrefix(String anycastPrefix) { for (String prefix : anycastPrefix.split(",")) { prefixes.put(SimpleString.toSimpleString(prefix), RoutingType.ANYCAST); } } @Override public void setMulticastPrefix(String multicastPrefix) { for (String prefix : multicastPrefix.split(",")) { prefixes.put(SimpleString.toSimpleString(prefix), RoutingType.MULTICAST); } } @Override public Map<SimpleString, RoutingType> getPrefixes() { return prefixes; } public List<DestinationInfo> getTemporaryDestinations() { List<DestinationInfo> total = new ArrayList<>(); for (OpenWireConnection connection : connections) { total.addAll(connection.getTemporaryDestinations()); } return total; } }