/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2014, Telestax Inc and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * */ package org.restcomm.media.rtp.secure; import java.io.IOException; import java.net.NetworkInterface; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import org.apache.log4j.Logger; import org.bouncycastle.crypto.tls.DatagramTransport; /** * Datagram Transport implementation that uses NIO instead of bocking IO. * * @author Henrique Rosa * */ public class NioUdpTransport implements DatagramTransport { private static final Logger logger = Logger.getLogger(NioUdpTransport.class); public static final int DEFAULT_MTU = 1500; private final static int MIN_IP_OVERHEAD = 20; private final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64; private final static int UDP_OVERHEAD = 8; public final static int MAX_DELAY = 10000; private final DatagramChannel channel; private int mtu; private final int receiveLimit; private final int sendLimit; private long startTime; public NioUdpTransport(DatagramChannel channel) { if (!channel.isConnected()) { throw new IllegalArgumentException("The datagram channel must be connected"); } this.channel = channel; try { NetworkInterface inet = NetworkInterface.getByInetAddress(channel.socket().getLocalAddress()); this.mtu = inet == null ? 0 : inet.getMTU(); } catch (SocketException e) { logger.warn("Could not discover Network Interface for current channel, setting MTU to " + DEFAULT_MTU + ". Reason: "+ e.getMessage(), e); this.mtu = DEFAULT_MTU; } this.receiveLimit = Math.max(0, mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD); this.sendLimit = Math.max(0, mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD); this.startTime = System.currentTimeMillis(); } public void start() { this.startTime = System.currentTimeMillis(); } @Override public int getReceiveLimit() throws IOException { return this.receiveLimit; } @Override public int getSendLimit() throws IOException { return this.sendLimit; } @Override public int receive(byte[] buf, int off, int len, int waitMillis) throws IOException { // MEDIA-48: DTLS handshake thread does not terminate // https://telestax.atlassian.net/browse/MEDIA-48 if (this.hasTimeout()) { throw new IllegalStateException("Handshake is taking too long! (>" + MAX_DELAY + "ms"); } ByteBuffer buffer = ByteBuffer.wrap(buf, off, len); return this.channel.read(buffer); } @Override public void send(byte[] buf, int off, int len) throws IOException { if (len > getSendLimit()) { /* * RFC 4347 4.1.1. "If the application attempts to send a record * larger than the MTU, the DTLS implementation SHOULD generate an * error, thus avoiding sending a packet which will be fragmented." */ // TODO Exception } // MEDIA-48: DTLS handshake thread does not terminate // https://telestax.atlassian.net/browse/MEDIA-48 if (this.hasTimeout()) { throw new IllegalStateException("Handshake is taking too long! (>" + MAX_DELAY + "ms"); } ByteBuffer buffer = ByteBuffer.wrap(buf, off, len); this.channel.send(buffer, this.channel.getRemoteAddress()); } @Override public void close() throws IOException { if (this.channel.isOpen()) { this.channel.close(); } } private boolean hasTimeout() { return (System.currentTimeMillis() - this.startTime) > MAX_DELAY; } }