/* * JBoss, Home of Professional Open Source * Copyright 2011, Red Hat, Inc. and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.restcomm.media.network.deprecated; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.spi.SelectorProvider; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.log4j.Logger; import org.restcomm.media.network.deprecated.channel.Channel; import org.restcomm.media.network.deprecated.channel.NetworkChannel; import org.restcomm.media.scheduler.Scheduler; import org.restcomm.media.scheduler.ServiceScheduler; /** * Manager responsible for scheduling I/O operations over UDP. * * Important! Any CPU-bound action here are illegal! * * @author yulian oifa * @author Henrique Rosa (henrique.rosa@telestax.com) */ public class UdpManager { private final static Logger logger = Logger.getLogger(UdpManager.class); // Core elements private final Scheduler scheduler; private final PortManager portManager; private final PortManager localPortManager; // UDP Manager properties private static final int PORT_ANY = -1; private static final String INET_UNKNOWN = "unknown"; private static final String LOCALHOST = "127.0.0.1"; private String inet; private String bindAddress; private String localBindAddress; private String externalAddress; private byte[] localNetwork; private String localNetworkString; private IPAddressType currNetworkType; private byte[] localSubnet; private String localSubnetString; private IPAddressType currSubnetType; private Boolean useSbc; private int rtpTimeout; // in seconds! private volatile boolean active; private final Object LOCK; private final List<Selector> selectors; private List<PollTask> pollTasks; private List<Future<?>> pollTaskFutures; private AtomicInteger currSelectorIndex; public UdpManager(Scheduler scheduler, PortManager portManager, PortManager localPortManager) { // Core elements this.portManager = portManager; this.localPortManager = localPortManager; // UDP Manager properties this.inet = INET_UNKNOWN; this.bindAddress = LOCALHOST; this.localBindAddress = LOCALHOST; this.externalAddress = ""; this.useSbc = false; this.rtpTimeout = 0; this.active = false; this.LOCK = new Object(); // UDP manager tasks this.scheduler = scheduler; this.selectors = new ArrayList<Selector>(ServiceScheduler.POOL_SIZE); this.pollTasks = new ArrayList<PollTask>(ServiceScheduler.POOL_SIZE); this.pollTaskFutures = new ArrayList<Future<?>>(ServiceScheduler.POOL_SIZE); this.currSelectorIndex = new AtomicInteger(0); } public Scheduler getScheduler() { return scheduler; } /** * Modify bind address. * * @param address the IP address as character string. */ public void setBindAddress(String address) { this.bindAddress = address; } /** * Gets the bind address. * * @return the IP address as character string. */ public String getBindAddress() { return bindAddress; } /** * Modify bind address. * * @param address the IP address as character string. */ public void setLocalBindAddress(String address) { this.localBindAddress = address; } /** * Gets the bind address. * * @return the IP address as character string. */ public String getLocalBindAddress() { return localBindAddress; } public String getExternalAddress() { return externalAddress; } public void setExternalAddress(String externalAddress) { this.externalAddress = externalAddress; } /** * Modify rtp timeout. * * @param rtpTimeout the time in seconds. */ public void setRtpTimeout(int rtpTimeout) { this.rtpTimeout = rtpTimeout; } /** * Gets the rtp timeout. * * @return the rtptimeout as integer. */ public int getRtpTimeout() { return this.rtpTimeout; } /** * Set the local network address * * @param address the IP address as character string. */ public void setLocalNetwork(String localNetwork) { IPAddressType currNetworkType = IPAddressCompare.getAddressType(localNetwork); this.currNetworkType = currNetworkType; this.localNetworkString = localNetwork; if (currNetworkType == IPAddressType.IPV4) { this.localNetwork = IPAddressCompare.addressToByteArrayV4(localNetwork); } else if (currNetworkType == IPAddressType.IPV6) { this.localNetwork = IPAddressCompare.addressToByteArrayV6(localNetwork); } } public String getLocalNetwork() { return localNetworkString; } /** * Set the local network address * * @param address the IP subnet as character string. */ public void setLocalSubnet(String localSubnet) { IPAddressType currSubnetType = IPAddressCompare.getAddressType(localSubnet); this.currSubnetType = currSubnetType; this.localSubnetString = localSubnet; if (currSubnetType == IPAddressType.IPV4) { this.localSubnet = IPAddressCompare.addressToByteArrayV4(localSubnet); } else if (currSubnetType == IPAddressType.IPV6) { this.localSubnet = IPAddressCompare.addressToByteArrayV6(localSubnet); } } public String getLocalSubnet() { return localSubnetString; } /** * Set the useSbc property * * @param useSbc whether to use sbc or not */ public void setUseSbc(Boolean useSbc) { this.useSbc = useSbc; } public PortManager getPortManager() { return portManager; } /** * Gets the low boundary of available range. * * @return low min port number */ public int getLowestPort() { return portManager.getLowest(); } /** * Gets the upper boundary of available range. * * @retun min port number */ public int getHighestPort() { return portManager.getLowest(); } public void addSelector(Selector selector) { synchronized (LOCK) { if (!this.selectors.contains(selector)) { this.selectors.add(selector); PollTask pollTask = new PollTask(selector); this.pollTasks.add(pollTask); ScheduledFuture<?> future = this.scheduler.scheduleWithFixedDelay(pollTask, 0L, 2L, TimeUnit.MILLISECONDS); this.pollTaskFutures.add(future); } } } public boolean connectImmediately(InetSocketAddress address) { if (!useSbc) { return true; } boolean connectImmediately = false; byte[] addressValue = address.getAddress().getAddress(); if (currSubnetType == IPAddressType.IPV4 && currNetworkType == IPAddressType.IPV4) { if (IPAddressCompare.isInRangeV4(localNetwork, localSubnet, addressValue)) { connectImmediately = true; } } else if (currSubnetType == IPAddressType.IPV6 && currNetworkType == IPAddressType.IPV6) { if (IPAddressCompare.isInRangeV6(localNetwork, localSubnet, addressValue)) { connectImmediately = true; } } return connectImmediately; } /** * Opens and binds new datagram channel. * * @param handler the packet handler implementation * @param port the port to bind to * @return datagram channel * @throws IOException */ @Deprecated public DatagramChannel open(ProtocolHandler handler) throws IOException { DatagramChannel channel = DatagramChannel.open(); channel.configureBlocking(false); int index = currSelectorIndex.getAndIncrement(); SelectionKey key = channel.register(selectors.get(index % selectors.size()), SelectionKey.OP_READ); key.attach(handler); handler.setKey(key); return channel; } public SelectionKey open(Channel channel) throws IOException { DatagramChannel dataChannel = DatagramChannel.open(); dataChannel.configureBlocking(false); int index = currSelectorIndex.getAndIncrement(); SelectionKey key = dataChannel.register(selectors.get(index % selectors.size()), SelectionKey.OP_READ); key.attach(channel); return key; } public void register(NetworkChannel channel) throws IOException { int index = this.currSelectorIndex.getAndIncrement(); channel.register(this.selectors.get(index % this.selectors.size()), SelectionKey.OP_READ); } @Deprecated public SelectionKey open(DatagramChannel dataChannel, Channel channel) throws IOException { // Get a selector int index = currSelectorIndex.getAndIncrement(); Selector selector = selectors.get(index % selectors.size()); // Register the channel under the chosen selector SelectionKey key = dataChannel.register(selector, SelectionKey.OP_READ); // Attach the multiplexer to the key key.attach(channel); return key; } @Deprecated public void open(DatagramChannel channel, ProtocolHandler handler) throws IOException { // Get a selector int index = currSelectorIndex.getAndIncrement(); Selector selector = selectors.get(index % selectors.size()); // Register the channel under the chosen selector SelectionKey key = channel.register(selector, SelectionKey.OP_READ); // Attach the protocol handler to the key key.attach(handler); handler.setKey(key); } public void bind(DatagramChannel channel, int port, boolean local) throws IOException { if (local) { bindLocal(channel, port); } else { bind(channel, port); } } /** * Binds socket to global bind address and specified port. * * @param channel the channel * @param port the port to bind to * @throws IOException */ public void bind(DatagramChannel channel, int port) throws IOException { // select port if wildcarded if (port == PORT_ANY) { port = portManager.next(); } // try bind IOException ex = null; for (int q = 0; q < 100; q++) { try { channel.bind(new InetSocketAddress(bindAddress, port)); ex = null; break; } catch (IOException e) { ex = e; logger.info("Failed trying to bind " + bindAddress + ":" + port); port = portManager.next(); } } if (ex != null) { throw ex; } } /** * Binds socket to global bind address and specified port. * * @param channel the channel * @param port the port to bind to * @throws IOException */ public void bindLocal(DatagramChannel channel, int port) throws IOException { // select port if wildcarded if (port == PORT_ANY) { port = localPortManager.next(); } // try bind IOException ex = null; for (int q = 0; q < 100; q++) { try { channel.bind(new InetSocketAddress(localBindAddress, port)); ex = null; break; } catch (IOException e) { ex = e; logger.info("Failed trying to bind " + localBindAddress + ":" + port); port = localPortManager.next(); } } if (ex != null) { throw ex; } } private void generateTasks() throws IOException { for (int i = 0; i < ServiceScheduler.POOL_SIZE; i++) { this.selectors.add(SelectorProvider.provider().openSelector()); PollTask pollTask = new PollTask(this.selectors.get(i)); this.pollTasks.add(pollTask); ScheduledFuture<?> future = this.scheduler.scheduleWithFixedDelay(pollTask, 0L, 2L, TimeUnit.MILLISECONDS); this.pollTaskFutures.add(future); } } private void stopTasks() { for (Future<?> future : this.pollTaskFutures) { future.cancel(false); } this.pollTaskFutures.clear(); } private void closeSelectors() { for (int i = 0; i < this.selectors.size(); i++) { Selector selector = this.selectors.get(i); if (selector != null && selector.isOpen()) { try { selector.close(); } catch (Exception e) { logger.error("Could not close selector " + i, e); } } } } private void cleanResources() { this.pollTasks.clear(); this.selectors.clear(); } /** * Starts polling the network. */ public void start() { synchronized (LOCK) { if (!this.active) { this.active = true; logger.info("Starting UDP Manager"); try { generateTasks(); logger.info("Initialized UDP interface[" + inet + "]: bind address=" + bindAddress); } catch (IOException e) { logger.error("An error occurred while initializing the polling tasks", e); stop(); } } } } /** * Stops polling the network. */ public void stop() { synchronized (LOCK) { if (this.active) { this.active = false; logger.info("Stopping UDP Manager"); stopTasks(); closeSelectors(); cleanResources(); logger.info("UDP Manager has stopped"); } } } /** * Runnable task for polling UDP channels */ private class PollTask implements Runnable { private final Selector localSelector; public PollTask(Selector selector) { this.localSelector = selector; } @Override public void run() { if (active) { try { // Select channels enabled for reading operation (without blocking!) int selected = localSelector.selectNow(); if (selected == 0) { return; } } catch (IOException e) { logger.error("Could not select channels from Selector!"); } // Iterate over selected channels Iterator<SelectionKey> it = localSelector.selectedKeys().iterator(); while (it.hasNext() && active) { SelectionKey key = it.next(); it.remove(); // Get references to channel and associated RTP socket DatagramChannel udpChannel = (DatagramChannel) key.channel(); Object attachment = key.attachment(); if (attachment == null) { continue; } try { if (attachment instanceof ProtocolHandler) { // Legacy - MGCP channel ProtocolHandler handler = (ProtocolHandler) key.attachment(); if (!udpChannel.isOpen()) { handler.onClosed(); continue; } // do read if (key.isReadable()) { handler.receive(udpChannel); } } else if (attachment instanceof Channel) { Channel channel = (Channel) attachment; // Perform an operation only if channel is open and key is valid if (udpChannel.isOpen()) { if (key.isValid()) { channel.receive(); if (channel.hasPendingData()) { channel.send(); } } } else { // Close data channel if datagram channel is closed channel.close(); } } else if (attachment instanceof NetworkChannel) { NetworkChannel channel = (NetworkChannel) attachment; // Perform an operation only if channel is open and key is valid if (udpChannel.isOpen()) { if (key.isValid()) { channel.receive(); } } else { // Close data channel if datagram channel is closed channel.close(); } } } catch (Exception e) { logger.error("An unexpected problem occurred while reading from channel.", e); } } localSelector.selectedKeys().clear(); } } } }