// **********************************************************************
//
// Copyright (c) 2003-2010 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************
package IceInternal;
final class UdpTransceiver implements Transceiver
{
public java.nio.channels.SelectableChannel
fd()
{
assert(_fd != null);
return _fd;
}
public int
initialize()
{
//
// Nothing to do.
//
return SocketOperation.None;
}
public void
close()
{
assert(_fd != null);
if(_state >= StateConnected && _traceLevels.network >= 1)
{
String s = "closing udp connection\n" + toString();
_logger.trace(_traceLevels.networkCat, s);
}
try
{
_fd.close();
}
catch(java.io.IOException ex)
{
}
_fd = null;
}
public boolean
write(Buffer buf)
{
assert(buf.b.position() == 0);
assert(_fd != null && _state >= StateConnected);
// The caller is supposed to check the send size before by calling checkSendSize
assert(java.lang.Math.min(_maxPacketSize, _sndSize - _udpOverhead) >= buf.size());
int ret = 0;
while(true)
{
try
{
if(_state == StateConnected)
{
ret = _fd.write(buf.b);
}
else
{
if(_peerAddr == null)
{
throw new Ice.SocketException(); // No peer has sent a datagram yet.
}
ret = _fd.send(buf.b, _peerAddr);
}
break;
}
catch(java.nio.channels.AsynchronousCloseException ex)
{
Ice.ConnectionLostException se = new Ice.ConnectionLostException();
se.initCause(ex);
throw se;
}
catch(java.net.PortUnreachableException ex)
{
Ice.ConnectionLostException se = new Ice.ConnectionLostException();
se.initCause(ex);
throw se;
}
catch(java.io.InterruptedIOException ex)
{
continue;
}
catch(java.io.IOException ex)
{
Ice.SocketException se = new Ice.SocketException();
se.initCause(ex);
throw se;
}
}
if(ret == 0)
{
return false;
}
if(_traceLevels.network >= 3)
{
String s = "sent " + ret + " bytes via udp\n" + toString();
_logger.trace(_traceLevels.networkCat, s);
}
if(_stats != null)
{
_stats.bytesSent(type(), ret);
}
assert(ret == buf.b.limit());
return true;
}
public boolean
read(Buffer buf, Ice.BooleanHolder moreData)
{
assert(buf.b.position() == 0);
moreData.value = false;
final int packetSize = java.lang.Math.min(_maxPacketSize, _rcvSize - _udpOverhead);
buf.resize(packetSize, true);
buf.b.position(0);
int ret = 0;
while(true)
{
try
{
java.net.SocketAddress peerAddr = _fd.receive(buf.b);
if(peerAddr == null || buf.b.position() == 0)
{
return false;
}
_peerAddr = (java.net.InetSocketAddress)peerAddr;
ret = buf.b.position();
break;
}
catch(java.nio.channels.AsynchronousCloseException ex)
{
Ice.ConnectionLostException se = new Ice.ConnectionLostException();
se.initCause(ex);
throw se;
}
catch(java.net.PortUnreachableException ex)
{
Ice.ConnectionLostException se = new Ice.ConnectionLostException();
se.initCause(ex);
throw se;
}
catch(java.io.InterruptedIOException ex)
{
continue;
}
catch(java.io.IOException ex)
{
Ice.ConnectionLostException se = new Ice.ConnectionLostException();
se.initCause(ex);
throw se;
}
}
if(_state == StateNeedConnect)
{
//
// If we must connect, we connect to the first peer that sends us a packet.
//
Network.doConnect(_fd, _peerAddr);
_state = StateConnected;
if(_traceLevels.network >= 1)
{
String s = "connected udp socket\n" + toString();
_logger.trace(_traceLevels.networkCat, s);
}
}
if(_traceLevels.network >= 3)
{
String s = "received " + ret + " bytes via udp\n" + toString();
_logger.trace(_traceLevels.networkCat, s);
}
if(_stats != null)
{
_stats.bytesReceived(type(), ret);
}
buf.resize(ret, true);
buf.b.position(ret);
return true;
}
public String
type()
{
return "udp";
}
public String
toString()
{
if(_fd == null)
{
return "<closed>";
}
String s;
if(_state == StateNotConnected)
{
java.net.DatagramSocket socket = ((java.nio.channels.DatagramChannel)_fd).socket();
s = "local address = " + Network.addrToString((java.net.InetSocketAddress)socket.getLocalSocketAddress());
if(_peerAddr != null)
{
s += "\nremote address = " + Network.addrToString(_peerAddr);
}
}
else
{
s = Network.fdToString(_fd);
}
if(_mcastAddr != null)
{
s += "\nmulticast address = " + Network.addrToString(_mcastAddr);
}
return s;
}
public Ice.ConnectionInfo
getInfo()
{
assert(_fd != null);
Ice.UDPConnectionInfo info = new Ice.UDPConnectionInfo();
java.net.DatagramSocket socket = _fd.socket();
info.localAddress = socket.getLocalAddress().getHostAddress();
info.localPort = socket.getLocalPort();
if(_state == StateNotConnected)
{
if(_peerAddr != null)
{
info.remoteAddress = _peerAddr.getAddress().getHostAddress();
info.remotePort = _peerAddr.getPort();
}
else
{
info.remoteAddress = "";
info.remotePort = -1;
}
}
else
{
if(socket.getInetAddress() != null)
{
info.remoteAddress = socket.getInetAddress().getHostAddress();
info.remotePort = socket.getPort();
}
else
{
info.remoteAddress = "";
info.remotePort = -1;
}
}
if(_mcastAddr != null)
{
info.mcastAddress = _mcastAddr.getAddress().getHostAddress();
info.mcastPort = _mcastAddr.getPort();
}
else
{
info.mcastAddress = "";
info.mcastPort = -1;
}
return info;
}
public void
checkSendSize(Buffer buf, int messageSizeMax)
{
if(buf.size() > messageSizeMax)
{
Ex.throwMemoryLimitException(buf.size(), messageSizeMax);
}
//
// The maximum packetSize is either the maximum allowable UDP packet size, or
// the UDP send buffer size (which ever is smaller).
//
final int packetSize = java.lang.Math.min(_maxPacketSize, _sndSize - _udpOverhead);
if(packetSize < buf.size())
{
throw new Ice.DatagramLimitException();
}
}
public final int
effectivePort()
{
return _addr.getPort();
}
//
// Only for use by UdpEndpoint
//
UdpTransceiver(Instance instance, java.net.InetSocketAddress addr, String mcastInterface, int mcastTtl)
{
_traceLevels = instance.traceLevels();
_logger = instance.initializationData().logger;
_stats = instance.initializationData().stats;
_state = StateNeedConnect;
_addr = addr;
try
{
_fd = Network.createUdpSocket();
setBufSize(instance);
Network.setBlock(_fd, false);
Network.doConnect(_fd, _addr);
_state = StateConnected; // We're connected now
if(_addr.getAddress().isMulticastAddress())
{
configureMulticast(null, mcastInterface, mcastTtl);
}
if(_traceLevels.network >= 1)
{
String s = "starting to send udp packets\n" + toString();
_logger.trace(_traceLevels.networkCat, s);
}
}
catch(Ice.LocalException ex)
{
_fd = null;
throw ex;
}
}
//
// Only for use by UdpEndpoint
//
UdpTransceiver(Instance instance, String host, int port, String mcastInterface, boolean connect)
{
_traceLevels = instance.traceLevels();
_logger = instance.initializationData().logger;
_stats = instance.initializationData().stats;
_state = connect ? StateNeedConnect : StateNotConnected;
try
{
_fd = Network.createUdpSocket();
setBufSize(instance);
Network.setBlock(_fd, false);
_addr = Network.getAddressForServer(host, port, instance.protocolSupport());
if(_traceLevels.network >= 2)
{
String s = "attempting to bind to udp socket " + Network.addrToString(_addr);
_logger.trace(_traceLevels.networkCat, s);
}
if(_addr.getAddress().isMulticastAddress())
{
Network.setReuseAddress(_fd, true);
_mcastAddr = _addr;
if(System.getProperty("os.name").startsWith("Windows") ||
System.getProperty("java.vm.name").startsWith("OpenJDK"))
{
//
// Windows does not allow binding to the mcast address itself
// so we bind to INADDR_ANY (0.0.0.0) instead. As a result,
// bi-directional connection won't work because the source
// address won't be the multicast address and the client will
// therefore reject the datagram.
//
int protocol =
_mcastAddr.getAddress().getAddress().length == 4 ? Network.EnableIPv4 : Network.EnableIPv6;
_addr = Network.getAddressForServer("", port, protocol);
}
_addr = Network.doBind(_fd, _addr);
if(port == 0)
{
_mcastAddr = new java.net.InetSocketAddress(_mcastAddr.getAddress(), _addr.getPort());
}
configureMulticast(_mcastAddr, mcastInterface, -1);
}
else
{
if(!System.getProperty("os.name").startsWith("Windows"))
{
//
// Enable SO_REUSEADDR on Unix platforms to allow
// re-using the socket even if it's in the TIME_WAIT
// state. On Windows, this doesn't appear to be
// necessary and enabling SO_REUSEADDR would actually
// not be a good thing since it allows a second
// process to bind to an address even it's already
// bound by another process.
//
// TODO: using SO_EXCLUSIVEADDRUSE on Windows would
// probably be better but it's only supported by recent
// Windows versions (XP SP2, Windows Server 2003).
//
Network.setReuseAddress(_fd, true);
}
_addr = Network.doBind(_fd, _addr);
}
if(_traceLevels.network >= 1)
{
StringBuffer s = new StringBuffer("starting to receive udp packets\n");
s.append(toString());
java.util.List<String> interfaces =
Network.getHostsForEndpointExpand(_addr.getAddress().getHostAddress(), instance.protocolSupport(),
true);
if(!interfaces.isEmpty())
{
s.append("\nlocal interfaces: ");
s.append(IceUtilInternal.StringUtil.joinString(interfaces, ", "));
}
_logger.trace(_traceLevels.networkCat, s.toString());
}
}
catch(Ice.LocalException ex)
{
_fd = null;
throw ex;
}
}
private synchronized void
setBufSize(Instance instance)
{
assert(_fd != null);
for(int i = 0; i < 2; ++i)
{
String direction;
String prop;
int dfltSize;
if(i == 0)
{
direction = "receive";
prop = "Ice.UDP.RcvSize";
dfltSize = Network.getRecvBufferSize(_fd);
_rcvSize = dfltSize;
}
else
{
direction = "send";
prop = "Ice.UDP.SndSize";
dfltSize = Network.getSendBufferSize(_fd);
_sndSize = dfltSize;
}
//
// Get property for buffer size and check for sanity.
//
int sizeRequested = instance.initializationData().properties.getPropertyAsIntWithDefault(prop, dfltSize);
if(sizeRequested < (_udpOverhead + IceInternal.Protocol.headerSize))
{
_logger.warning("Invalid " + prop + " value of " + sizeRequested + " adjusted to " + dfltSize);
sizeRequested = dfltSize;
}
if(sizeRequested != dfltSize)
{
//
// Try to set the buffer size. The kernel will silently adjust
// the size to an acceptable value. Then read the size back to
// get the size that was actually set.
//
int sizeSet;
if(i == 0)
{
Network.setRecvBufferSize(_fd, sizeRequested);
_rcvSize = Network.getRecvBufferSize(_fd);
sizeSet = _rcvSize;
}
else
{
Network.setSendBufferSize(_fd, sizeRequested);
_sndSize = Network.getSendBufferSize(_fd);
sizeSet = _sndSize;
}
//
// Warn if the size that was set is less than the requested size.
//
if(sizeSet < sizeRequested)
{
_logger.warning("UDP " + direction + " buffer size: requested size of "
+ sizeRequested + " adjusted to " + sizeSet);
}
}
}
}
//
// The NIO classes do not support multicast, at least not directly. This method works around
// that limitation by using reflection to configure the file descriptor of a DatagramChannel for
// multicast operation. Specifically, an instance of java.net.PlainDatagramSocketImpl is used
// to (temporarily) wrap the channel's file descriptor.
//
private void
configureMulticast(java.net.InetSocketAddress group, String interfaceAddr, int ttl)
{
try
{
Class<?> cls;
cls = Util.findClass("java.net.PlainDatagramSocketImpl", null);
if(cls == null)
{
throw new Ice.SocketException();
}
java.lang.reflect.Constructor<?> c = cls.getDeclaredConstructor((Class<?>[])null);
c.setAccessible(true);
java.net.DatagramSocketImpl socketImpl = (java.net.DatagramSocketImpl)c.newInstance((Object[])null);
//
// We have to invoke the protected create() method on the PlainDatagramSocketImpl object so
// that this hack works properly when IPv6 is enabled on Windows.
//
java.lang.reflect.Method m;
try
{
m = cls.getDeclaredMethod("create", (Class<?>[])null);
m.setAccessible(true);
m.invoke(socketImpl);
}
catch(java.lang.NoSuchMethodException ex) // OpenJDK
{
}
cls = Util.findClass("sun.nio.ch.DatagramChannelImpl", null);
if(cls == null)
{
throw new Ice.SocketException();
}
java.lang.reflect.Field channelFd = cls.getDeclaredField("fd");
channelFd.setAccessible(true);
java.lang.reflect.Field socketFd = java.net.DatagramSocketImpl.class.getDeclaredField("fd");
socketFd.setAccessible(true);
socketFd.set(socketImpl, channelFd.get(_fd));
try
{
java.net.NetworkInterface intf = null;
if(interfaceAddr.length() != 0)
{
intf = java.net.NetworkInterface.getByName(interfaceAddr);
if(intf == null)
{
java.net.InetSocketAddress addr = Network.getAddress(interfaceAddr, 0, Network.EnableIPv4);
intf = java.net.NetworkInterface.getByInetAddress(addr.getAddress());
}
}
if(group != null)
{
Class<?>[] types;
Object[] args;
try
{
types = new Class<?>[]{ java.net.SocketAddress.class, java.net.NetworkInterface.class };
m = socketImpl.getClass().getDeclaredMethod("joinGroup", types);
args = new Object[]{ group, intf };
}
catch(java.lang.NoSuchMethodException ex) // OpenJDK
{
types = new Class<?>[]{ java.net.InetAddress.class, java.net.NetworkInterface.class };
m = socketImpl.getClass().getDeclaredMethod("join", types);
args = new Object[]{ group.getAddress(), intf };
}
m.setAccessible(true);
m.invoke(socketImpl, args);
}
else if(intf != null)
{
Class<?>[] types = new Class<?>[]{ Integer.TYPE, Object.class };
try
{
m = socketImpl.getClass().getDeclaredMethod("setOption", types);
}
catch(java.lang.NoSuchMethodException ex) // OpenJDK
{
m = socketImpl.getClass().getDeclaredMethod("socketSetOption", types);
}
m.setAccessible(true);
Object[] args = new Object[]{ Integer.valueOf(java.net.SocketOptions.IP_MULTICAST_IF2), intf };
m.invoke(socketImpl, args);
}
if(ttl != -1)
{
Class<?>[] types = new Class<?>[]{ Integer.TYPE };
m = java.net.DatagramSocketImpl.class.getDeclaredMethod("setTimeToLive", types);
m.setAccessible(true);
Object[] args = new Object[]{ Integer.valueOf(ttl) };
m.invoke(socketImpl, args);
}
}
finally
{
socketFd.set(socketImpl, null);
}
}
catch(Exception ex)
{
Ice.SocketException se = new Ice.SocketException();
se.initCause(ex);
throw se;
}
}
protected synchronized void
finalize()
throws Throwable
{
IceUtilInternal.Assert.FinalizerAssert(_fd == null);
super.finalize();
}
private TraceLevels _traceLevels;
private Ice.Logger _logger;
private Ice.Stats _stats;
private int _state;
private int _rcvSize;
private int _sndSize;
private java.nio.channels.DatagramChannel _fd;
private java.net.InetSocketAddress _addr;
private java.net.InetSocketAddress _mcastAddr = null;
private java.net.InetSocketAddress _peerAddr = null;
//
// The maximum IP datagram size is 65535. Subtract 20 bytes for the IP header and 8 bytes for the UDP header
// to get the maximum payload.
//
private final static int _udpOverhead = 20 + 8;
private final static int _maxPacketSize = 65535 - _udpOverhead;
private static final int StateNeedConnect = 0;
private static final int StateConnected = 1;
private static final int StateNotConnected = 2;
}