/*_########################################################################## _## _## Copyright (C) 2013 Kaito Yamada _## _########################################################################## */ package com.github.kaitoy.sneo.network.protocol; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; import org.pcap4j.packet.IcmpV6CommonPacket; import org.pcap4j.packet.IpV6Packet; import org.pcap4j.packet.IpV6SimpleFlowLabel; import org.pcap4j.packet.IpV6SimpleTrafficClass; import org.pcap4j.packet.Packet; import org.pcap4j.packet.SimpleBuilder; import org.pcap4j.packet.TcpPacket; import org.pcap4j.packet.UdpPacket; import org.pcap4j.packet.namednumber.IpNumber; import org.pcap4j.packet.namednumber.IpVersion; import org.pcap4j.util.ByteArrays; import org.pcap4j.util.MacAddress; import com.github.kaitoy.sneo.network.NetworkInterface; import com.github.kaitoy.sneo.network.NifIpAddress; import com.github.kaitoy.sneo.network.NifIpV6Address; public final class IpV6Helper { public static final Inet6Address NODE_LOCAL_ALL_NODES_ADDRESS; public static final Inet6Address LINK_LOCAL_ALL_NODES_ADDRESS; public static final Inet6Address LINK_LOCAL_ALL_ROUTERS_ADDRESS; public static final Inet6Address UNSPECIFIED_ADDRESS; static { try { NODE_LOCAL_ALL_NODES_ADDRESS = (Inet6Address)InetAddress.getByName("FF01:0:0:0:0:0:0:1"); LINK_LOCAL_ALL_NODES_ADDRESS = (Inet6Address)InetAddress.getByName("FF02:0:0:0:0:0:0:1"); LINK_LOCAL_ALL_ROUTERS_ADDRESS = (Inet6Address)InetAddress.getByName("FF02:0:0:0:0:0:0:2"); UNSPECIFIED_ADDRESS = (Inet6Address)InetAddress.getByName("::"); } catch (UnknownHostException e) { throw new AssertionError("Never get here."); } } private IpV6Helper() { throw new AssertionError(); } public static boolean matchesDestination(Packet packet, Inet6Address addr) { IpV6Packet ipv6Packet = packet.get(IpV6Packet.class); if (ipv6Packet == null) { throw new IllegalArgumentException(packet.toString()); } Inet6Address dstAddr = ipv6Packet.getHeader().getDstAddr(); if (dstAddr.equals(addr)) { return true; } if (dstAddr.equals(LINK_LOCAL_ALL_NODES_ADDRESS)) { return true; } if (dstAddr.equals(LINK_LOCAL_ALL_ROUTERS_ADDRESS)) { return true; } return false; } public static boolean matchesDestination(Packet packet, NetworkInterface nif) { for (NifIpAddress nifAddr: nif.getIpAddresses()) { if (nifAddr instanceof NifIpV6Address) { NifIpV6Address nifV6Addr = (NifIpV6Address)nifAddr; if (matchesDestination(packet, nifV6Addr.getIpAddr())) { return true; } } } return false; } public static boolean isSameNetwork( Inet6Address addr1, Inet6Address addr2, int prefixLength ) { if (prefixLength < 0 || prefixLength > 128) { throw new IllegalArgumentException( "Invalid prefix length: " + prefixLength ); } if (prefixLength <= 64) { long addr1Bitmap = ByteArrays.getLong(addr1.getAddress(), 0); long addr2Bitmap = ByteArrays.getLong(addr2.getAddress(), 0); return isSamePrefix(addr1Bitmap, addr2Bitmap, prefixLength); } else { long addr1Bitmap1h = ByteArrays.getLong(addr1.getAddress(), 0); long addr2Bitmap1h = ByteArrays.getLong(addr2.getAddress(), 0); if (addr1Bitmap1h != addr2Bitmap1h) { return false; } long addr1Bitmap2h = ByteArrays.getLong(addr1.getAddress(), 16); long addr2Bitmap2h = ByteArrays.getLong(addr2.getAddress(), 16); return isSamePrefix(addr1Bitmap2h, addr2Bitmap2h, prefixLength - 64); } } public static boolean isSameNetwork( Inet6Address addr1, NetworkInterface nif ) { for (NifIpAddress nifAddr: nif.getIpAddresses()) { if (nifAddr instanceof NifIpV6Address) { NifIpV6Address nifV6Addr = (NifIpV6Address)nifAddr; if (isSameNetwork(addr1, nifV6Addr.getIpAddr(), nifV6Addr.getPrefixLength())) { return true; } } } return false; } private static boolean isSamePrefix(long bitmap1, long bitmap2, int prefixLength) { long mask = 0; for (int i = 0; i < prefixLength; i++) { mask <<= 1; mask++; } for (int i = 0; i < 64 - prefixLength ; i++) { mask <<= 1; } return (bitmap1 & mask) == (bitmap2 & mask); } public static IpV6RoutingTable newRoutingTable() { return new IpV6RoutingTable(); } public static Inet6Address getNextHop( Inet6Address dstIpAddr, IpV6RoutingTable ipV6RoutingTable ) { return ipV6RoutingTable.getNextHop(dstIpAddr); } public static IpV6Packet decrementTtl( IpV6Packet packet ) throws TimeoutException { int hopLimit = packet.getHeader().getHopLimitAsInt(); if (hopLimit <= 1) { throw new TimeoutException(); } hopLimit--; IpV6Packet.Builder b = packet.getBuilder().hopLimit((byte)hopLimit); return b.build(); } public static Inet6Address generateLinkLocalIpV6Address(MacAddress macAddr) { try { return generateIpV6Address((Inet6Address)InetAddress.getByName("fe80::"), macAddr); } catch (UnknownHostException e) { throw new AssertionError("Never get here."); } } public static Inet6Address generateIpV6Address(Inet6Address prefix, MacAddress macAddr) { byte[] addr = new byte[16]; System.arraycopy(prefix.getAddress(), 0, addr, 0, 8); System.arraycopy(convertToEui64(macAddr), 0, addr, 8, 8); try { return (Inet6Address)InetAddress.getByAddress(addr); } catch (UnknownHostException e) { throw new AssertionError("Never get here."); } } public static byte[] convertToEui64(MacAddress macAddr) { byte[] eui64 = new byte[8]; byte[] oui = macAddr.getOui().valueAsByteArray(); oui[0] = (byte)(oui[0] ^ 0x02); System.arraycopy(oui, 0, eui64, 0, 3); eui64[3] = (byte)0xFF; eui64[4] = (byte)0xFE; System.arraycopy(macAddr.getAddress(), 3, eui64, 5, 3); return eui64; } public static Inet6Address generateSolicitedNodeMulticastAddress(Inet6Address targetAddr) { byte[] addr = new byte[16]; addr[0] = (byte)0xFF; addr[1] = (byte)0x02; addr[11] = (byte)0x01; addr[12] = (byte)0xFF; System.arraycopy(targetAddr.getAddress(), 13, addr, 13, 3); try { return (Inet6Address)InetAddress.getByAddress(addr); } catch (UnknownHostException e) { throw new AssertionError("Never get here."); } } public static MacAddress generateNeighborSolicitationMacAddress(Inet6Address dstAddr) { byte[] addr = new byte[6]; addr[0] = (byte)0x33; addr[1] = (byte)0x33; System.arraycopy(dstAddr.getAddress(), 12, addr, 2, 4); return MacAddress.getByAddress(addr); } public static IpV6Packet pack( final Packet payload, Inet6Address src, Inet6Address dst, int hopLimit, short id ) { IpNumber nextHeader; if (payload instanceof UdpPacket) { nextHeader = IpNumber.UDP; } else if (payload instanceof IcmpV6CommonPacket) { nextHeader = IpNumber.ICMPV6; } else if (payload instanceof TcpPacket) { nextHeader = IpNumber.TCP; } else { throw new AssertionError(); } IpV6Packet.Builder builder = new IpV6Packet.Builder(); return builder.version(IpVersion.IPV6) .trafficClass(IpV6SimpleTrafficClass.newInstance((byte)0)) .flowLabel(IpV6SimpleFlowLabel.newInstance(0)) .nextHeader(nextHeader) .hopLimit((byte)hopLimit) .srcAddr(src) .dstAddr(dst) .payloadBuilder(new SimpleBuilder(payload)) .correctLengthAtBuild(true) .build(); } public static Inet6Address getNextAddress(Inet6Address addr, int prefixLength) { byte[] rawAddr = addr.getAddress(); rawAddr[15]++; try { return (Inet6Address)InetAddress.getByAddress(rawAddr); } catch (UnknownHostException e) { throw new AssertionError("Never get here."); } } public static class IpV6RoutingTable { private final Map<Inet6Address, IpV6RoutingTableEntry> entries = new HashMap<Inet6Address, IpV6RoutingTableEntry>(); private IpV6RoutingTable() {} public void addRoute( Inet6Address dst, int prefixLength, Inet6Address gw, int metric ) { synchronized (entries) { entries.put( dst, new IpV6RoutingTableEntry(dst, prefixLength, gw, metric) ); } } private Inet6Address getNextHop(Inet6Address dst) { Collection<IpV6RoutingTableEntry> candidates = null; synchronized (entries) { IpV6RoutingTableEntry justMatchedEntry = entries.get(dst); if (justMatchedEntry != null) { return justMatchedEntry.gw; } candidates = entries.values(); } IpV6RoutingTableEntry mostMatchedEntry = null; for (IpV6RoutingTableEntry entry: candidates) { if (isSameNetwork(entry.dst, dst, entry.prefixLength)) { if (mostMatchedEntry == null) { mostMatchedEntry = entry; } else if (entry.prefixLength > mostMatchedEntry.prefixLength) { mostMatchedEntry = entry; } else if ( entry.prefixLength == mostMatchedEntry.prefixLength && entry.metric < mostMatchedEntry.metric ) { mostMatchedEntry = entry; } } } if (mostMatchedEntry == null) { return null; } return mostMatchedEntry.gw; } public List<IpV6RoutingTableEntry> getEntries() { return new ArrayList<IpV6RoutingTableEntry>(entries.values()); } public final class IpV6RoutingTableEntry { private final Inet6Address dst; private final int prefixLength; private final Inet6Address gw; private final int metric; private IpV6RoutingTableEntry( Inet6Address dst, int prefixLength, Inet6Address gw, int metric ) { this.dst = dst; this.prefixLength = prefixLength; this.gw = gw; this.metric = metric; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("DST[").append(dst).append("/").append(prefixLength).append("] ") .append("GW[").append(gw).append("] ") .append("METRIC[").append(metric).append("]"); return sb.toString(); } } } }