/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library 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 library 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 library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.net.ipv4.udp; import gnu.java.net.PlainDatagramSocketImpl; import java.io.IOException; import java.net.DatagramSocket; import java.net.DatagramSocketImplFactory; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketImplFactory; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.HashMap; import java.util.Random; import org.apache.log4j.Logger; import org.jnode.driver.net.NetworkException; import org.jnode.net.SocketBuffer; import org.jnode.net.ipv4.IPv4Constants; import org.jnode.net.ipv4.IPv4Header; import org.jnode.net.ipv4.IPv4Protocol; import org.jnode.net.ipv4.IPv4Service; import org.jnode.net.ipv4.icmp.ICMPUtils; import org.jnode.vm.objects.Statistics; /** * @author epr * @author Martin Husted Hartvig (hagar@jnode.org) */ public class UDPProtocol implements IPv4Protocol, IPv4Constants { /** * My logger */ private static final Logger log = Logger.getLogger(UDPProtocol.class); /** * The underlying IP service */ private final IPv4Service ipService; /** * Socket bindings (lport, socket) */ private final HashMap<Integer, UDPDatagramSocketImpl> sockets = new HashMap<Integer, UDPDatagramSocketImpl>(); /** * DatagramSocketImplFactor instance */ private final UDPDatagramSocketImplFactory dsiFactory; /** * My statistics */ private final UDPStatistics stat = new UDPStatistics(); /** * ICMP utility */ private final ICMPUtils icmp; /** * for random listener ports */ private final Integer zero = 0; private final Random random = new Random(); private final int startRandom = 1024; private final int stopRandom = (65535 - startRandom); /** * Create a new instance * * @param ipService */ public UDPProtocol(IPv4Service ipService) throws NetworkException { this.ipService = ipService; this.icmp = new ICMPUtils(ipService); try { dsiFactory = new UDPDatagramSocketImplFactory(this); try { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { public Object run() throws IOException { DatagramSocket.setDatagramSocketImplFactory(dsiFactory); PlainDatagramSocketImpl.setUDPFactory(dsiFactory); return null; } }); } catch (SecurityException ex) { log.error("No permission to set DatagramSocketImplFactory", ex); } catch (PrivilegedActionException ex) { throw new NetworkException(ex.getException()); } } catch (IOException ex) { throw new NetworkException(ex); } } /** * @see org.jnode.net.ipv4.IPv4Protocol#getName() */ public String getName() { return "udp"; } /** * @see org.jnode.net.ipv4.IPv4Protocol#getProtocolID() */ public int getProtocolID() { return IPPROTO_UDP; } /** * @see org.jnode.net.ipv4.IPv4Protocol#receive(org.jnode.net.SocketBuffer) */ public void receive(SocketBuffer skbuf) throws SocketException { stat.ipackets.inc(); final UDPHeader hdr = new UDPHeader(skbuf); if (!hdr.isChecksumOk()) { stat.badsum.inc(); return; } // Set the UDP header in the buffer-field skbuf.setTransportLayerHeader(hdr); // Remove the UDP header from the head of the buffer skbuf.pull(hdr.getLength()); // Trim the buffer up to the length in the UDP header skbuf.trim(hdr.getDataLength()); // Test the length of the buffer to the datalength in the header. if (skbuf.getSize() < hdr.getDataLength()) { stat.badlen.inc(); return; } // Syslog.debug("Found UDP: " + hdr); deliver(hdr, skbuf); } /** * Process an ICMP error message that has been received and matches this * protocol. The skbuf is position directly after the ICMP header (thus * contains the error IP header and error transport layer header). The * transportLayerHeader property of skbuf is set to the ICMP message header. * * @param skbuf * @throws SocketException */ public void receiveError(SocketBuffer skbuf) throws SocketException { // TODO handle ICMP errors in UDP } /** * Gets the SocketImplFactory of this protocol. * * @throws SocketException If this protocol is not Socket based. */ public SocketImplFactory getSocketImplFactory() throws SocketException { throw new SocketException("UDP is packet based"); } /** * Gets the DatagramSocketImplFactory of this protocol. */ public DatagramSocketImplFactory getDatagramSocketImplFactory() { return dsiFactory; } /** * Deliver a given packet to all interested sockets. * * @param hdr * @param skbuf */ private synchronized void deliver(UDPHeader hdr, SocketBuffer skbuf) throws SocketException { final Integer lport = hdr.getDstPort(); final IPv4Header ipHdr = (IPv4Header) skbuf.getNetworkLayerHeader(); final UDPDatagramSocketImpl socket = (UDPDatagramSocketImpl) sockets.get(lport); if (socket != null) { final InetAddress laddr = socket.getLocalAddress(); if (laddr.isAnyLocalAddress() || laddr.equals(ipHdr.getDestination().toInetAddress())) { if (socket.deliverReceived(skbuf)) { return; } } } stat.noport.inc(); if (ipHdr.getDestination().isBroadcast()) { stat.noportbcast.inc(); } // Send a port unreachable back icmp.sendPortUnreachable(skbuf); } /** * Register a datagram socket * * @param socket */ protected synchronized void bind(UDPDatagramSocketImpl socket) throws SocketException { Integer lport = socket.getLocalPort(); if (lport.compareTo(zero) != 0 && sockets.containsKey(lport)) { throw new SocketException("Port already bound (" + lport + ')'); } else { Integer ran; while (lport.compareTo(zero) == 0) { ran = random.nextInt(stopRandom) + startRandom; if (!sockets.containsKey(ran)) { // Should we have one stop condition more?? lport = ran; socket.setLocalPort(lport); } } sockets.put(lport, socket); } } /** * Unregister a datagram socket * * @param socket */ protected synchronized void unbind(UDPDatagramSocketImpl socket) { final Integer lport = socket.getLocalPort(); if (sockets.get(lport) == socket) { sockets.remove(lport); } } /** * Send an UDP packet * * @param skbuf */ protected void send(IPv4Header ipHdr, UDPHeader udpHdr, SocketBuffer skbuf) throws SocketException { skbuf.setTransportLayerHeader(udpHdr); udpHdr.prefixTo(skbuf); ipService.transmit(ipHdr, skbuf); stat.opackets.inc(); } /** * @see org.jnode.net.ipv4.IPv4Protocol#getStatistics() */ public Statistics getStatistics() { return stat; } }