/* * Copyright 2014-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onlab.packet; import org.onlab.packet.ipv6.Authentication; import org.onlab.packet.ipv6.DestinationOptions; import org.onlab.packet.ipv6.EncapSecurityPayload; import org.onlab.packet.ipv6.Fragment; import org.onlab.packet.ipv6.HopByHopOptions; import org.onlab.packet.ipv6.IExtensionHeader; import org.onlab.packet.ipv6.Routing; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static org.onlab.packet.PacketUtils.checkInput; /** * Implements IPv6 packet format. (RFC 2460) */ public class IPv6 extends IP implements IExtensionHeader { public static final byte FIXED_HEADER_LENGTH = 40; // bytes public static final byte PROTOCOL_TCP = 0x6; public static final byte PROTOCOL_UDP = 0x11; public static final byte PROTOCOL_ICMP6 = 0x3A; public static final byte PROTOCOL_HOPOPT = 0x00; public static final byte PROTOCOL_ROUTING = 0x2B; public static final byte PROTOCOL_FRAG = 0x2C; public static final byte PROTOCOL_ESP = 0x32; public static final byte PROTOCOL_AH = 0x33; public static final byte PROTOCOL_DSTOPT = 0x3C; public static final byte LINK_LOCAL_0 = (byte) 0xfe; public static final byte LINK_LOCAL_1 = (byte) 0x80; public static final Map<Byte, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP = new HashMap<>(); static { IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ICMP6, ICMP6.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_TCP, TCP.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_UDP, UDP.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_HOPOPT, HopByHopOptions.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ROUTING, Routing.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_FRAG, Fragment.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_ESP, EncapSecurityPayload.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_AH, Authentication.deserializer()); IPv6.PROTOCOL_DESERIALIZER_MAP.put(IPv6.PROTOCOL_DSTOPT, DestinationOptions.deserializer()); } protected byte version; protected byte trafficClass; protected int flowLabel; protected short payloadLength; protected byte nextHeader; protected byte hopLimit; protected byte[] sourceAddress = new byte[Ip6Address.BYTE_LENGTH]; protected byte[] destinationAddress = new byte[Ip6Address.BYTE_LENGTH]; /** * Default constructor that sets the version to 6. */ public IPv6() { super(); this.version = 6; } @Override public byte getVersion() { return this.version; } @Override public IPv6 setVersion(final byte version) { this.version = version; return this; } /** * Gets traffic class. * * @return the traffic class */ public byte getTrafficClass() { return this.trafficClass; } /** * Sets traffic class. * * @param trafficClass the traffic class to set * @return this */ public IPv6 setTrafficClass(final byte trafficClass) { this.trafficClass = trafficClass; return this; } /** * Gets flow label. * * @return the flow label */ public int getFlowLabel() { return this.flowLabel; } /** * Sets flow label. * * @param flowLabel the flow label to set * @return this */ public IPv6 setFlowLabel(final int flowLabel) { this.flowLabel = flowLabel; return this; } @Override public byte getNextHeader() { return this.nextHeader; } @Override public IPv6 setNextHeader(final byte nextHeader) { this.nextHeader = nextHeader; return this; } /** * Gets hop limit. * * @return the hop limit */ public byte getHopLimit() { return this.hopLimit; } /** * Sets hop limit. * * @param hopLimit the hop limit to set * @return this */ public IPv6 setHopLimit(final byte hopLimit) { this.hopLimit = hopLimit; return this; } /** * Gets source address. * * @return the IPv6 source address */ public byte[] getSourceAddress() { return this.sourceAddress; } /** * Sets source address. * * @param sourceAddress the IPv6 source address to set * @return this */ public IPv6 setSourceAddress(final byte[] sourceAddress) { this.sourceAddress = Arrays.copyOfRange(sourceAddress, 0, Ip6Address.BYTE_LENGTH); return this; } /** * Gets destination address. * * @return the IPv6 destination address */ public byte[] getDestinationAddress() { return this.destinationAddress; } /** * Sets destination address. * * @param destinationAddress the IPv6 destination address to set * @return this */ public IPv6 setDestinationAddress(final byte[] destinationAddress) { this.destinationAddress = Arrays.copyOfRange(destinationAddress, 0, Ip6Address.BYTE_LENGTH); return this; } @Override public byte[] serialize() { byte[] payloadData = null; if (this.payload != null) { this.payload.setParent(this); payloadData = this.payload.serialize(); } this.payloadLength = 0; if (payloadData != null) { this.payloadLength = (short) payloadData.length; } final byte[] data = new byte[FIXED_HEADER_LENGTH + payloadLength]; final ByteBuffer bb = ByteBuffer.wrap(data); bb.putInt((this.version & 0xf) << 28 | (this.trafficClass & 0xff) << 20 | this.flowLabel & 0xfffff); bb.putShort(this.payloadLength); bb.put(this.nextHeader); bb.put(this.hopLimit); bb.put(this.sourceAddress, 0, Ip6Address.BYTE_LENGTH); bb.put(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH); if (payloadData != null) { bb.put(payloadData); } return data; } @Override public IPacket deserialize(final byte[] data, final int offset, final int length) { final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); int iscratch; iscratch = bb.getInt(); this.version = (byte) (iscratch >> 28 & 0xf); this.trafficClass = (byte) (iscratch >> 20 & 0xff); this.flowLabel = iscratch & 0xfffff; this.payloadLength = bb.getShort(); this.nextHeader = bb.get(); this.hopLimit = bb.get(); bb.get(this.sourceAddress, 0, Ip6Address.BYTE_LENGTH); bb.get(this.destinationAddress, 0, Ip6Address.BYTE_LENGTH); Deserializer<? extends IPacket> deserializer; if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(this.nextHeader)) { deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(this.nextHeader); } else { deserializer = Data.deserializer(); } try { this.payload = deserializer.deserialize(data, bb.position(), bb.limit() - bb.position()); this.payload.setParent(this); } catch (DeserializationException e) { return this; } return this; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 2521; int result = super.hashCode(); ByteBuffer bb; bb = ByteBuffer.wrap(this.destinationAddress); for (int i = 0; i < 4; i++) { result = prime * result + bb.getInt(); } result = prime * result + this.trafficClass; result = prime * result + this.flowLabel; result = prime * result + this.hopLimit; result = prime * result + this.nextHeader; result = prime * result + this.payloadLength; bb = ByteBuffer.wrap(this.sourceAddress); for (int i = 0; i < 4; i++) { result = prime * result + bb.getInt(); } result = prime * result + this.version; return result; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (!(obj instanceof IPv6)) { return false; } final IPv6 other = (IPv6) obj; if (!Arrays.equals(this.destinationAddress, other.destinationAddress)) { return false; } if (this.trafficClass != other.trafficClass) { return false; } if (this.flowLabel != other.flowLabel) { return false; } if (this.hopLimit != other.hopLimit) { return false; } if (this.nextHeader != other.nextHeader) { return false; } if (this.payloadLength != other.payloadLength) { return false; } if (!Arrays.equals(this.sourceAddress, other.sourceAddress)) { return false; } return true; } /** * Deserializer function for IPv6 packets. * * @return deserializer function */ public static Deserializer<IPv6> deserializer() { return (data, offset, length) -> { checkInput(data, offset, length, FIXED_HEADER_LENGTH); IPv6 ipv6 = new IPv6(); ByteBuffer bb = ByteBuffer.wrap(data, offset, length); int iscratch = bb.getInt(); ipv6.version = (byte) (iscratch >> 28 & 0xf); ipv6.trafficClass = (byte) (iscratch >> 20 & 0xff); ipv6.flowLabel = iscratch & 0xfffff; ipv6.payloadLength = bb.getShort(); ipv6.nextHeader = bb.get(); ipv6.hopLimit = bb.get(); bb.get(ipv6.sourceAddress, 0, Ip6Address.BYTE_LENGTH); bb.get(ipv6.destinationAddress, 0, Ip6Address.BYTE_LENGTH); Deserializer<? extends IPacket> deserializer; if (IPv6.PROTOCOL_DESERIALIZER_MAP.containsKey(ipv6.nextHeader)) { deserializer = IPv6.PROTOCOL_DESERIALIZER_MAP.get(ipv6.nextHeader); } else { deserializer = Data.deserializer(); } int remainingLength = bb.limit() - bb.position(); int payloadLength = ipv6.payloadLength; int bytesToRead = (payloadLength <= remainingLength) ? payloadLength : remainingLength; ipv6.payload = deserializer.deserialize(data, bb.position(), bytesToRead); ipv6.payload.setParent(ipv6); return ipv6; }; } @Override public String toString() { return toStringHelper(getClass()) .add("version", Byte.toString(version)) .add("trafficClass", Byte.toString(trafficClass)) .add("flowLabel", Integer.toString(flowLabel)) .add("payloadLength", Short.toString(payloadLength)) .add("nextHeader", Byte.toString(nextHeader)) .add("hopLimit", Byte.toString(hopLimit)) .add("sourceAddress", Arrays.toString(sourceAddress)) .add("destinationAddress", Arrays.toString(destinationAddress)) .toString(); } /** * According to the RFC 4291, the solicitation node addresses are * formed by taking the low-order 24 bits of an address (unicast or anycast) * and appending those bits to the prefix FF02:0:0:0:0:1:FF00::/104. * * Solicited-Node Address: FF02:0:0:0:0:1:FFXX:XXXX * * @param targetIp the unicast or anycast address * @return the computed solicitation node address */ public static byte[] getSolicitNodeAddress(byte[] targetIp) { checkArgument(targetIp.length == Ip6Address.BYTE_LENGTH); return new byte[] { (byte) 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, (byte) 0xff, targetIp[targetIp.length - 3], targetIp[targetIp.length - 2], targetIp[targetIp.length - 1] }; } /** * According to the RFC 2464, an IPv6 packet with a multicast * destination address DST, consisting of the sixteen octets DST[1] * through DST[16], is transmitted to the Ethernet multicast address * whose first two octets are the value 3333 hexadecimal and whose last * four octets are the last four octets of DST. * * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |0 0 1 1 0 0 1 1|0 0 1 1 0 0 1 1| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | DST[13] | DST[14] | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | DST[15] | DST[16] | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * @param targetIp the multicast address. * @return the multicast mac address */ public static byte[] getMCastMacAddress(byte[] targetIp) { checkArgument(targetIp.length == Ip6Address.BYTE_LENGTH); return new byte[] { 0x33, 0x33, targetIp[targetIp.length - 4], targetIp[targetIp.length - 3], targetIp[targetIp.length - 2], targetIp[targetIp.length - 1], }; } /** * According to the RFC 4291, an IPv6 link local address is an IPv6 * unicast address that can be automatically configured on any interface * using the link-local prefix FE80::/10 (1111 1110 10) and the interface * identifier in the modified EUI-64 format. * * +----------------------------------------------------------------+ * | 10 bits | 54 bits | 64 bits | * +----------- +-------------------------+-------------------------+ * | 1111111010 | 0 | interface ID | * +----------- +-------------------------+-------------------------+ * * @param targetIp the ip address to verify * @return true if the ipv6 address is link local, * false otherwise */ public static boolean isLinkLocalAddress(byte[] targetIp) { checkArgument(targetIp.length == Ip6Address.BYTE_LENGTH); return (targetIp[0] & 0xff) == 0xfe && (targetIp[1] & 0xc0) == 0x80; } /** * Returns the auto-generated link local address using the * mac address as parameter. * * @param macAddress the mac address to use * @return the ipv6 link local address */ public static byte[] getLinkLocalAddress(byte[] macAddress) { checkArgument(macAddress.length == MacAddress.MAC_ADDRESS_LENGTH); return new byte[] { LINK_LOCAL_0, LINK_LOCAL_1, 0, 0, 0, 0, 0, 0, (byte) (macAddress[0] ^ (1 << 1)), macAddress[1], macAddress[2], (byte) 0xff, (byte) 0xfe, macAddress[3], macAddress[4], macAddress[5], }; } /** * Returns the mac address from the auto-generated * link local address. * * @param linkLocalAddress the ipv6 to use * @return the mac address */ public static byte[] getMacAddress(byte[] linkLocalAddress) { return !isLinkLocalAddress(linkLocalAddress) ? null : new byte[] { (byte) (linkLocalAddress[8] ^ (1 << 1)), linkLocalAddress[9], linkLocalAddress[10], linkLocalAddress[13], linkLocalAddress[14], linkLocalAddress[15], }; } }