/*
* $Id$
*
* Copyright (C) 2003-2014 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.layer;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import org.apache.log4j.Logger;
import org.jnode.driver.Device;
import org.jnode.driver.net.NetDeviceAPI;
import org.jnode.driver.net.NetworkException;
import org.jnode.net.HardwareAddress;
import org.jnode.net.InvalidLayerException;
import org.jnode.net.LayerAlreadyRegisteredException;
import org.jnode.net.NetworkLayer;
import org.jnode.net.NoSuchProtocolException;
import org.jnode.net.ProtocolAddress;
import org.jnode.net.SocketBuffer;
import org.jnode.net.TransportLayer;
import org.jnode.net.arp.ARPNetworkLayer;
import org.jnode.net.ethernet.EthernetConstants;
import org.jnode.net.ipv4.IPv4Address;
import org.jnode.net.ipv4.IPv4Constants;
import org.jnode.net.ipv4.IPv4FragmentList;
import org.jnode.net.ipv4.IPv4Header;
import org.jnode.net.ipv4.IPv4Protocol;
import org.jnode.net.ipv4.IPv4ProtocolAddressInfo;
import org.jnode.net.ipv4.IPv4RoutingTable;
import org.jnode.net.ipv4.IPv4Service;
import org.jnode.net.ipv4.icmp.ICMPProtocol;
import org.jnode.net.ipv4.raw.RAWProtocol;
import org.jnode.net.ipv4.tcp.TCPProtocol;
import org.jnode.net.ipv4.udp.UDPProtocol;
import org.jnode.net.ipv4.util.ResolverImpl;
import org.jnode.net.util.NetUtils;
import org.jnode.util.NumberUtils;
import org.jnode.vm.objects.Statistics;
/**
* @author epr
*/
public class IPv4NetworkLayer implements NetworkLayer, IPv4Constants, IPv4Service {
/**
* My logger
*/
private static final Logger log = Logger.getLogger(IPv4NetworkLayer.class);
private final HashMap<Integer, IPv4Protocol> protocols = new HashMap<Integer, IPv4Protocol>();
/**
* List of in-complete fragments
*/
private final HashMap<Object, IPv4FragmentList> fragments =
new HashMap<Object, IPv4FragmentList>();
/**
* System time of last call to removeDeadFragments
*/
private long lastFragmentCleanup = 0;
/**
* My statistics
*/
private final IPv4Statistics stat = new IPv4Statistics();
/**
* The routing table
*/
private final IPv4RoutingTable rt = new IPv4RoutingTable();
/**
* The sender
*/
private final IPv4Sender sender;
/**
* The ARP network layer
*/
private ARPNetworkLayer arp;
/**
* Initialize a new instance
*/
public IPv4NetworkLayer() throws NetworkException {
sender = new IPv4Sender(this);
registerProtocol(new ICMPProtocol(this));
registerProtocol(new TCPProtocol(this));
registerProtocol(new UDPProtocol(this));
registerProtocol(new RAWProtocol(this));
}
/**
* Gets the name of this type
*/
public String getName() {
return "ipv4";
}
/**
* Gets the protocol ID this packettype handles
*/
public int getProtocolID() {
return EthernetConstants.ETH_P_IP;
}
/**
* Can this packet type process packets received from the given device?
*/
public boolean isAllowedForDevice(Device dev) {
// For all devices
return true;
}
/**
* Process a packet that has been received and matches getType()
*
* @param skbuf
* @param deviceAPI
* @throws SocketException
*/
public void receive(SocketBuffer skbuf, NetDeviceAPI deviceAPI) throws SocketException {
// Update statistics
stat.ipackets.inc();
// Get IP header
final IPv4Header hdr = new IPv4Header(skbuf);
if (!hdr.isChecksumOk()) {
stat.badsum.inc();
return;
}
// Set the header object in the buffer-field
skbuf.setNetworkLayerHeader(hdr);
// Remove header from skbuf-data
skbuf.pull(hdr.getLength());
// Trim the end of the message, to we have a valid length
skbuf.trim(hdr.getDataLength());
// Now test if the size of the buffer equals the datalength in the
// header, if now ignore the packet
if (skbuf.getSize() < hdr.getDataLength()) {
stat.badlen.inc();
return;
}
// Update the ARP cache for the source address
updateARPCache(skbuf.getLinkLayerHeader().getSourceAddress(), hdr.getSourceAddress());
// Get my IP address
final IPv4ProtocolAddressInfo myAddrInfo =
(IPv4ProtocolAddressInfo) deviceAPI.getProtocolAddressInfo(getProtocolID());
if (myAddrInfo == null) {
stat.nodevaddr.inc();
}
// Should I process this packet, or is it for somebody else?
final IPv4Address dstAddr = hdr.getDestination();
final boolean shouldProcess;
if (myAddrInfo != null) {
shouldProcess = myAddrInfo.contains(dstAddr);
} else {
// I don't have an IP address yet, if the linklayer says
// it is for me, we'll process it, otherwise we'll drop it.
shouldProcess = !skbuf.getLinkLayerHeader().getDestinationAddress().isBroadcast();
}
if (!shouldProcess) {
// log.debug("IPPacket not for me, ignoring (dst=" + dstAddr + ")");
return;
}
// Is it a fragment?
if (hdr.isFragment()) {
// Yes it is a fragment
stat.fragments.inc();
deliverFragment(hdr, skbuf);
} else {
// It is a complete packet, find the protocol handler
// and let it do the rest
deliver(hdr, skbuf);
}
// Do a cleanup of the fragmentlist from time to time
final long now = System.currentTimeMillis();
if ((now - lastFragmentCleanup) >= (IP_FRAGTIMEOUT * 2)) {
removeDeadFragments();
}
}
/**
* Gets the routing table
*/
public IPv4RoutingTable getRoutingTable() {
return rt;
}
/**
* Deliver a packet to the corresponding protocol
*
* @param hdr
* @param skbuf
*/
private void deliver(IPv4Header hdr, SocketBuffer skbuf) throws SocketException {
final IPv4Protocol protocol;
try {
protocol = getProtocol(hdr.getProtocol());
protocol.receive(skbuf);
} catch (NoSuchProtocolException ex) {
log.debug("Found unknown IP src=" + hdr.getSource() + ", dst=" + hdr.getDestination() +
", prot=0x" + NumberUtils.hex(hdr.getProtocol(), 2));
}
}
/**
* Process the delivery of a fragment
*
* @param hdr
* @param skbuf
* @throws NetworkException
*/
private void deliverFragment(IPv4Header hdr, SocketBuffer skbuf) throws SocketException {
final Object key = hdr.getFragmentListKey();
final IPv4FragmentList flist = (IPv4FragmentList) fragments.get(key);
if (flist == null) {
// This is a fragment for a new list
fragments.put(key, new IPv4FragmentList(skbuf));
} else {
if (flist.isAlive()) {
flist.add(skbuf);
if (flist.isComplete()) {
// The fragmentlist is now complete, deliver it
final SocketBuffer pbuf = flist.getPacket();
final IPv4Header phdr = (IPv4Header) pbuf.getNetworkLayerHeader();
stat.reassembled.inc();
deliver(phdr, pbuf);
}
} else {
// Timeout of fragmentlist, destroy it
fragments.remove(key);
}
}
}
/**
* Remove all dead fragments from the fragment list
*/
private final void removeDeadFragments() {
final ArrayList<Object> deadFragmentKeys = new ArrayList<Object>();
// First collect all dead fragment keys
// Do not remove the directly, since that will create an error
// in the iterator.
for (IPv4FragmentList f : fragments.values()) {
if (!f.isAlive()) {
deadFragmentKeys.add(f.getKey());
}
}
if (!deadFragmentKeys.isEmpty()) {
// Now remove all dead fragments
for (Object key : deadFragmentKeys) {
fragments.remove(key);
}
// We're done
log.debug("Removed " + deadFragmentKeys.size() + " dead fragments");
}
// Update our last invocation timestamp
lastFragmentCleanup = System.currentTimeMillis();
}
/**
* Gets the protocol for a given ID
*
* @param protocolID
* @throws NoSuchProtocolException
* No protocol with the given ID was found.
*/
public IPv4Protocol getProtocol(int protocolID) throws NoSuchProtocolException {
final IPv4Protocol protocol;
protocol = (IPv4Protocol) protocols.get(protocolID);
if (protocol == null) {
throw new NoSuchProtocolException("with ID " + protocolID);
}
return protocol;
}
/**
* Register a protocol
*
* @param protocol
*/
protected void registerProtocol(IPv4Protocol protocol) {
protocols.put(protocol.getProtocolID(), protocol);
}
/**
* Unregister a protocol
*
* @param protocol
*/
protected void unregisterProtocol(IPv4Protocol protocol) {
protocols.remove(protocol.getProtocolID());
}
/**
* Register a transportlayer as possible destination of packets received by
* this networklayer
*
* @param layer
*/
public void registerTransportLayer(TransportLayer layer)
throws LayerAlreadyRegisteredException, InvalidLayerException {
if (layer instanceof IPv4Protocol) {
registerProtocol((IPv4Protocol) layer);
} else {
throw new InvalidLayerException("No IPv4Protocol");
}
}
/**
* Unregister a transportlayer
*
* @param layer
*/
public void unregisterTransportLayer(TransportLayer layer) {
if (layer instanceof IPv4Protocol) {
unregisterProtocol((IPv4Protocol) layer);
}
}
/**
* Gets all registered transport-layers
*/
public Collection<TransportLayer> getTransportLayers() {
final ArrayList<TransportLayer> result = new ArrayList<TransportLayer>(protocols.values());
return result;
}
/**
* Gets a registered transportlayer by its protocol ID.
*
* @param protocolID
*/
public TransportLayer getTransportLayer(int protocolID) throws NoSuchProtocolException {
return getProtocol(protocolID);
}
/**
* @see org.jnode.net.NetworkLayer#getStatistics()
*/
public Statistics getStatistics() {
return stat;
}
/**
* @see org.jnode.net.ipv4.IPv4Service#transmit(org.jnode.net.ipv4.IPv4Header,
* org.jnode.net.SocketBuffer)
*/
public void transmit(IPv4Header hdr, SocketBuffer skbuf) throws SocketException {
sender.transmit(hdr, skbuf);
}
/**
* Gets the protocol addresses for a given name, or null if not found.
*
* @param hostname
* @return the addresses or {@code null}
*/
public ProtocolAddress[] getHostByName(String hostname) {
try {
return ResolverImpl.getInstance().getByName(hostname);
} catch (UnknownHostException ex) {
return null;
}
}
private void updateARPCache(HardwareAddress hwAddr, ProtocolAddress pAddr) {
if (arp == null) {
try {
arp = (ARPNetworkLayer) NetUtils.getNLM().getNetworkLayer(EthernetConstants.ETH_P_ARP);
} catch (NoSuchProtocolException ex) {
log.error("Cannot find ARP layer", ex);
} catch (NetworkException ex) {
log.error("Cannot network layer manager", ex);
}
}
if (arp != null) {
arp.getCache().set(hwAddr, pAddr, true);
}
}
}