blob: 0b7d8fbcebc7604bca558b55f6d840f518a759a6 [file] [log] [blame]
/*******************************************************************************
* Copyright 2014 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 net.onrc.onos.core.packet;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import net.onrc.onos.core.util.SwitchPort;
import org.apache.commons.lang3.ArrayUtils;
import com.google.common.base.Charsets;
/**
* LLDP packets ONOS uses for discovery of physical network topology.
* Refer to IEEE Std 802.1ABTM-2009 for more information.
*
*/
public class OnosLldp extends LLDP {
// ON.Lab OUI and ONOS name for organizationally specific TLVs
static final byte[] ONLAB_OUI = {(byte) 0xa4, 0x23, 0x05};
public static final String ONOS_NAME = "ONOSVirteX";
static final byte[] LLDP_NICIRA = {0x01, 0x23, 0x20, 0x00, 0x00,
0x01};
static final byte[] LLDP_MULTICAST = {0x01, (byte) 0x80,
(byte) 0xc2, 0x00, 0x00, 0x0e};
static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff,
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
public static final short ETHERTYPE_VLAN = (short) 0x8100;
// TLV constants: type, size and subtype
// Organizationally specific TLV also have packet offset and contents of TLV
// header
private static final byte CHASSIS_TLV_TYPE = 1;
private static final byte CHASSIS_TLV_SIZE = 7;
private static final byte CHASSIS_TLV_SUBTYPE = 4;
private static final byte PORT_TLV_TYPE = 2;
private static final byte PORT_TLV_SIZE = 3;
private static final byte PORT_TLV_SUBTYPE = 2;
private static final byte TTL_TLV_TYPE = 3;
private static final byte TTL_TLV_SIZE = 2;
private static final byte NAME_TLV_TYPE = 127;
// 4 = OUI (3) + subtype (1)
private static final byte NAME_TLV_SIZE = (byte) (4 + OnosLldp.ONOS_NAME.length());
private static final byte NAME_TLV_SUBTYPE = 1;
private static final short NAME_TLV_OFFSET = 32;
private static final short NAME_TLV_HEADER = (short) ((NAME_TLV_TYPE << 9) | (NAME_TLV_SIZE & 0xff));
// Contents of full name TLV
private static final byte[] NAME_TLV = ByteBuffer.allocate(NAME_TLV_SIZE + 2)
.putShort(NAME_TLV_HEADER).put(ONLAB_OUI).put(NAME_TLV_SUBTYPE)
.put(ONOS_NAME.getBytes(Charsets.UTF_8)).array();
private static final byte DPID_TLV_TYPE = 127;
private static final byte DPID_TLV_SIZE = (byte) (12); // 12 = OUI (3) + subtype
// (1) + dpid (8)
private static final byte DPID_TLV_SUBTYPE = 2;
private static final short DPID_TLV_HEADER = (short) ((DPID_TLV_TYPE << 9) | DPID_TLV_SIZE);
// Contents of dpid TLV
// Note that this does *not* contain the actual dpid since we cannot match
// on it
private static final byte[] DPID_TLV = ByteBuffer.allocate(DPID_TLV_SIZE + 2 - 8)
.putShort(DPID_TLV_HEADER).put(ONLAB_OUI).put(DPID_TLV_SUBTYPE)
.array();
// Pre-built contents of both organizationally specific TLVs
private static final byte[] OUI_TLV = ArrayUtils.addAll(NAME_TLV, DPID_TLV);
// Default switch, port number and TTL
private static final byte[] DEFAULT_DPID = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00 };
private static final short DEFAULT_PORT = 0;
private static final short DEFAULT_TTL = 120; // in seconds
// Minimum and ONOS-generated LLDP packet sizes
private static final short MINIMUM_LLDP_SIZE = 61;
// Add 12 for 2-byte header of each TLV and a single EndOfLLDPTLV
private static final short ONOS_LLDP_SIZE = (short) (CHASSIS_TLV_SIZE
+ PORT_TLV_SIZE + TTL_TLV_SIZE + NAME_TLV_SIZE + DPID_TLV_SIZE + 12);
// Direction TLVs are used to indicate if the LLDPs were sent
// periodically or in response to a received LLDP
private static final byte TLV_DIRECTION_TYPE = 0x73;
private static final short TLV_DIRECTION_LENGTH = 1; // 1 byte
private static final byte[] TLV_DIRECTION_VALUE_FORWARD = {0x01};
private static final byte[] TLV_DIRECTION_VALUE_REVERSE = {0x02};
private static final LLDPTLV FORWARD_TLV
= new LLDPTLV().
setType(TLV_DIRECTION_TYPE).
setLength(TLV_DIRECTION_LENGTH).
setValue(TLV_DIRECTION_VALUE_FORWARD);
private static final LLDPTLV REVERSE_TLV
= new LLDPTLV().
setType(TLV_DIRECTION_TYPE).
setLength(TLV_DIRECTION_LENGTH).
setValue(TLV_DIRECTION_VALUE_REVERSE);
// Field offsets in ONOS-generated LLDP
private static final short ETHERTYPE_OFFSET = 12;
private static final short PORT_OFFSET = 26;
private static final short DPID_OFFSET = 54;
// Private member fields
// Byte arrays for TLV information string
private byte[] chassisId = new byte[CHASSIS_TLV_SIZE];
private byte[] portId = new byte[PORT_TLV_SIZE];
private byte[] ttl = new byte[TTL_TLV_SIZE];
private byte[] ouiName = new byte[NAME_TLV_SIZE];
private byte[] ouiDpid = new byte[DPID_TLV_SIZE];
// TLVs
private LLDPTLV chassisTLV;
private LLDPTLV portTLV;
private LLDPTLV ttlTLV;
private LLDPTLV ouiNameTLV;
private LLDPTLV ouiDpidTLV;
private List<LLDPTLV> optionalTLVList;
/**
* Instantiates a new ONOS LDDP message.
*/
public OnosLldp() {
// Create TLVs
this.chassisTLV = new LLDPTLV();
this.portTLV = new LLDPTLV();
this.ttlTLV = new LLDPTLV();
this.ouiNameTLV = new LLDPTLV();
this.ouiDpidTLV = new LLDPTLV();
this.optionalTLVList = new LinkedList<LLDPTLV>();
this.optionalTLVList.add(this.ouiNameTLV);
this.optionalTLVList.add(this.ouiDpidTLV);
// Add TLVs to LLDP packet
this.setChassisId(this.chassisTLV);
this.setPortId(this.portTLV);
this.setTtl(this.ttlTLV);
this.setOptionalTLVList(this.optionalTLVList);
// Set TLVs to default values
this.setChassisTLV(DEFAULT_DPID);
this.setPortTLV(DEFAULT_PORT);
this.setTTLTLV(DEFAULT_TTL);
this.setOUIName(OnosLldp.ONOS_NAME);
this.setOUIDpid(DEFAULT_DPID);
}
/**
* Sets chassis TLV. Note that we can only put 6 bytes in the chassis ID, so
* we use another organizationally specific TLV to put the full dpid (see
* setOUIDpid()).
*
* @param dpid the switch DPID
*/
private void setChassisTLV(final byte[] dpid) {
ByteBuffer bb = ByteBuffer.wrap(this.chassisId);
bb.put(CHASSIS_TLV_SUBTYPE);
for (int i = 2; i < 8; i++) {
bb.put(dpid[i]);
}
this.chassisTLV.setLength(CHASSIS_TLV_SIZE);
this.chassisTLV.setType(CHASSIS_TLV_TYPE);
this.chassisTLV.setValue(this.chassisId);
}
/**
* Sets port TLV.
*
* @param portNumber the port number
*/
private void setPortTLV(final short portNumber) {
ByteBuffer bb = ByteBuffer.wrap(this.portId);
bb.put(PORT_TLV_SUBTYPE);
bb.putShort(portNumber);
this.portTLV.setLength(PORT_TLV_SIZE);
this.portTLV.setType(PORT_TLV_TYPE);
this.portTLV.setValue(this.portId);
}
/**
* Sets Time To Live TLV.
*
* @param time the time to live
*/
private void setTTLTLV(final short time) {
ByteBuffer bb = ByteBuffer.wrap(this.ttl);
bb.putShort(time);
this.ttlTLV.setLength(TTL_TLV_SIZE);
this.ttlTLV.setType(TTL_TLV_TYPE);
this.ttlTLV.setValue(this.ttl);
}
/**
* Sets organizationally specific TLV for ONOS name (subtype 1).
*
* @param name the name
*/
private void setOUIName(final String name) {
ByteBuffer bb = ByteBuffer.wrap(ouiName);
bb.put(OnosLldp.ONLAB_OUI);
bb.put(NAME_TLV_SUBTYPE);
bb.put(name.getBytes(Charsets.UTF_8));
this.ouiNameTLV.setLength(NAME_TLV_SIZE);
this.ouiNameTLV.setType(NAME_TLV_TYPE);
this.ouiNameTLV.setValue(ouiName);
}
/**
* Sets organizationally specific TLV for ONOS full dpid (subtype 2).
*
* @param dpid the switch DPID
*/
private void setOUIDpid(final byte[] dpid) {
ByteBuffer bb = ByteBuffer.wrap(ouiDpid);
bb.put(OnosLldp.ONLAB_OUI);
bb.put(DPID_TLV_SUBTYPE);
bb.put(dpid);
this.ouiDpidTLV.setLength(DPID_TLV_SIZE);
this.ouiDpidTLV.setType(DPID_TLV_TYPE);
this.ouiDpidTLV.setValue(ouiDpid);
}
/**
* Sets switch DPID in LLDP packet.
*
* @param dpid the switch dpid
*/
public void setSwitch(Long dpid) {
final byte[] byteDpid = ByteBuffer.allocate(8).putLong(dpid)
.array();
this.setChassisTLV(byteDpid);
this.setOUIDpid(byteDpid);
}
/**
* Sets the port number in LLDP packet.
*
* @param portNumber the port number
*/
public void setPort(short portNumber) {
this.setPortTLV(portNumber);
}
/**
* Sets whether this is a forward or reverse LLDP.
*
* @param isReverse true if reverse, false if forward
*/
public void setReverse(boolean isReverse) {
optionalTLVList.add((isReverse) ? REVERSE_TLV : FORWARD_TLV);
}
/**
* Serializes full LLDP packet to byte array.
*
* @return the serialized packet
*/
@Override
public byte[] serialize() {
return super.serialize();
}
/**
* Checks if LLDP packet has correct size, LLDP multicast address, and
* ethertype. Packet assumed to have Ethernet header.
*
* @param packet full packet starting from the Ethernet header
* @return true if packet is LLDP, false otherwise
*/
public static boolean isLLDP(final byte[] packet) {
// Does packet exist and does it have the mininum size?
if (packet == null || packet.length < MINIMUM_LLDP_SIZE) {
return false;
}
// Packet has LLDP multicast destination address?
final ByteBuffer bb = ByteBuffer.wrap(packet);
final byte[] dst = new byte[6];
bb.get(dst);
if (!(Arrays.equals(dst, OnosLldp.LLDP_NICIRA)
|| Arrays.equals(dst, OnosLldp.LLDP_MULTICAST) || Arrays.equals(
dst, OnosLldp.BDDP_MULTICAST))) {
return false;
}
// Fetch ethertype, skip VLAN tag if it's there
short etherType = bb.getShort(ETHERTYPE_OFFSET);
if (etherType == ETHERTYPE_VLAN) {
etherType = bb.getShort(ETHERTYPE_OFFSET + 4);
}
// Check ethertype
if (etherType == Ethernet.TYPE_LLDP) {
return true;
}
if (etherType == Ethernet.TYPE_BSN) {
return true;
}
return false;
}
/**
* Checks if packet has size of ONOS-generated LLDP, and correctness of two
* organizationally specific TLVs that use ON.Lab's OUI. Assumes packet is
* valid LLDP packet
*
* @param packet full packet starting from the Ethernet header
* @return true if this is an ONOS-generated LLDP, otherwise false
*/
public static boolean isOnosLldp(byte[] packet) {
if (packet.length < ONOS_LLDP_SIZE) {
return false;
}
// Extra offset due to VLAN tag
final ByteBuffer bb = ByteBuffer.wrap(packet);
int offset = 0;
if (bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_LLDP
&& bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_BSN) {
offset = 4;
}
// Compare packet's organizationally specific TLVs to the expected
// values
for (int i = 0; i < OUI_TLV.length; i++) {
if (packet[NAME_TLV_OFFSET + offset + i] != OUI_TLV[i]) {
return false;
}
}
return true;
}
/**
* Extracts dpid and port from ONOS-generated LLDP packet.
*
* @param packet full packet started at the Ethernet header
* @return switchport switch and port info from the DPID and Port TLVs
*/
public static SwitchPort extractSwitchPort(final byte[] packet) {
final ByteBuffer bb = ByteBuffer.wrap(packet);
// Extra offset due to VLAN tag
int offset = 0;
if (bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_LLDP
&& bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_BSN) {
offset = 4;
}
final short port = bb.getShort(PORT_OFFSET + offset);
final long dpid = bb.getLong(DPID_OFFSET + offset);
return new SwitchPort(dpid, port);
}
/**
* Checks if the LLDP is a reverse LLDP (i.e. sent in response to receiving
* an LLDP on the link). This information is stored in the Direction TLV.
*
* @param lldp parsed LLDP packet
* @return true if the LLDP is a reverse LLDP, otherwise false
*/
public static boolean isReverse(final LLDP lldp) {
for (LLDPTLV lldpTlv : lldp.getOptionalTLVList()) {
if ((lldpTlv.getType() == TLV_DIRECTION_TYPE) &&
Arrays.equals(lldpTlv.getValue(), TLV_DIRECTION_VALUE_REVERSE)) {
return true;
}
}
return false;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!super.equals(other)) {
return false;
}
//
// NOTE: Subclasses are are considered as change of identity, hence
// equals() will return false if the class type doesn't match.
//
if (getClass() != other.getClass()) {
return false;
}
OnosLldp otherLldp = (OnosLldp) other;
if (!this.chassisTLV.equals(otherLldp.chassisTLV)) {
return false;
}
if (!this.portTLV.equals(otherLldp.portTLV)) {
return false;
}
if (!this.ttlTLV.equals(otherLldp.ttlTLV)) {
return false;
}
if (!this.ouiNameTLV.equals(otherLldp.ouiNameTLV)) {
return false;
}
if (!this.ouiDpidTLV.equals(otherLldp.ouiDpidTLV)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + chassisTLV.hashCode();
result = prime * result + portTLV.hashCode();
result = prime * result + ttlTLV.hashCode();
result = prime * result + ouiNameTLV.hashCode();
result = prime * result + ouiDpidTLV.hashCode();
return result;
}
}