| /* |
| * Copyright 2014-present Open Networking Foundation |
| * |
| * 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 com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import org.apache.commons.lang.ArrayUtils; |
| import org.slf4j.Logger; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.charset.StandardCharsets; |
| import java.security.InvalidKeyException; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| |
| import javax.crypto.Mac; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| import static org.onlab.packet.LLDPOrganizationalTLV.OUI_LENGTH; |
| import static org.onlab.packet.LLDPOrganizationalTLV.SUBTYPE_LENGTH; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * ONOS LLDP containing organizational TLV for ONOS device discovery. |
| */ |
| public class ONOSLLDP extends LLDP { |
| |
| private static final Logger log = getLogger(ONOSLLDP.class); |
| |
| public static final String DEFAULT_DEVICE = "INVALID"; |
| public static final String DEFAULT_NAME = "ONOS Discovery"; |
| |
| protected static final byte NAME_SUBTYPE = 1; |
| protected static final byte DEVICE_SUBTYPE = 2; |
| protected static final byte DOMAIN_SUBTYPE = 3; |
| protected static final byte TIMESTAMP_SUBTYPE = 4; |
| protected static final byte SIG_SUBTYPE = 5; |
| |
| private static final short NAME_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; |
| private static final short DEVICE_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; |
| private static final short DOMAIN_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; |
| private static final short TIMESTAMP_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; |
| private static final short SIG_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; |
| |
| private final HashMap<Byte, LLDPOrganizationalTLV> opttlvs = Maps.newHashMap(); |
| |
| // 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 TTL_TLV_TYPE = 3; |
| private static final byte PORT_DESC_TLV_TYPE = 4; |
| |
| private final byte[] ttlValue = new byte[] {0, 0x78}; |
| |
| // Only needs to be accessed from LinkProbeFactory. |
| public ONOSLLDP(byte... subtype) { |
| super(); |
| for (byte st : subtype) { |
| opttlvs.put(st, new LLDPOrganizationalTLV()); |
| } |
| // guarantee the following (name and device) TLVs exist |
| opttlvs.putIfAbsent(NAME_SUBTYPE, new LLDPOrganizationalTLV()); |
| opttlvs.putIfAbsent(DEVICE_SUBTYPE, new LLDPOrganizationalTLV()); |
| setName(DEFAULT_NAME); |
| setDevice(DEFAULT_DEVICE); |
| |
| setOptionalTLVList(Lists.newArrayList(opttlvs.values())); |
| setTtl(new LLDPTLV().setType(TTL_TLV_TYPE) |
| .setLength((short) ttlValue.length) |
| .setValue(ttlValue)); |
| } |
| |
| private ONOSLLDP(LLDP lldp) { |
| this.portId = lldp.getPortId(); |
| this.chassisId = lldp.getChassisId(); |
| this.ttl = lldp.getTtl(); |
| this.optionalTLVList = lldp.getOptionalTLVList(); |
| } |
| |
| public void setName(String name) { |
| LLDPOrganizationalTLV nametlv = opttlvs.get(NAME_SUBTYPE); |
| nametlv.setLength((short) (name.length() + NAME_LENGTH)); |
| nametlv.setInfoString(name); |
| nametlv.setSubType(NAME_SUBTYPE); |
| nametlv.setOUI(MacAddress.ONOS.oui()); |
| } |
| |
| public void setDevice(String device) { |
| LLDPOrganizationalTLV devicetlv = opttlvs.get(DEVICE_SUBTYPE); |
| devicetlv.setInfoString(device); |
| devicetlv.setLength((short) (device.length() + DEVICE_LENGTH)); |
| devicetlv.setSubType(DEVICE_SUBTYPE); |
| devicetlv.setOUI(MacAddress.ONOS.oui()); |
| } |
| |
| public void setDomainInfo(String domainId) { |
| LLDPOrganizationalTLV domaintlv = opttlvs.get(DOMAIN_SUBTYPE); |
| if (domaintlv == null) { |
| // maybe warn people not to set this if remote probes aren't. |
| return; |
| } |
| domaintlv.setInfoString(domainId); |
| domaintlv.setLength((short) (domainId.length() + DOMAIN_LENGTH)); |
| domaintlv.setSubType(DOMAIN_SUBTYPE); |
| domaintlv.setOUI(MacAddress.ONOS.oui()); |
| } |
| |
| public void setChassisId(final ChassisId chassisId) { |
| MacAddress chassisMac = MacAddress.valueOf(chassisId.value()); |
| byte[] chassis = ArrayUtils.addAll(new byte[] {CHASSIS_TLV_SUBTYPE}, |
| chassisMac.toBytes()); |
| |
| LLDPTLV chassisTLV = new LLDPTLV(); |
| chassisTLV.setLength(CHASSIS_TLV_SIZE); |
| chassisTLV.setType(CHASSIS_TLV_TYPE); |
| chassisTLV.setValue(chassis); |
| this.setChassisId(chassisTLV); |
| } |
| |
| public void setPortId(final int portNumber) { |
| byte[] port = ArrayUtils.addAll(new byte[] {PORT_TLV_COMPONENT_SUBTYPE}, |
| String.valueOf(portNumber).getBytes(StandardCharsets.UTF_8)); |
| |
| LLDPTLV portTLV = new LLDPTLV(); |
| portTLV.setLength((short) port.length); |
| portTLV.setType(PORT_TLV_TYPE); |
| portTLV.setValue(port); |
| this.setPortId(portTLV); |
| } |
| |
| public void setPortName(final String portName) { |
| byte[] port = ArrayUtils.addAll(new byte[] {PORT_TLV_INTERFACE_NAME_SUBTYPE}, |
| portName.getBytes(StandardCharsets.UTF_8)); |
| |
| LLDPTLV portTLV = new LLDPTLV(); |
| portTLV.setLength((short) port.length); |
| portTLV.setType(PORT_TLV_TYPE); |
| portTLV.setValue(port); |
| this.setPortId(portTLV); |
| } |
| |
| public void setTimestamp(long timestamp) { |
| LLDPOrganizationalTLV tmtlv = opttlvs.get(TIMESTAMP_SUBTYPE); |
| if (tmtlv == null) { |
| return; |
| } |
| tmtlv.setInfoString(ByteBuffer.allocate(8).putLong(timestamp).array()); |
| tmtlv.setLength((short) (8 + TIMESTAMP_LENGTH)); |
| tmtlv.setSubType(TIMESTAMP_SUBTYPE); |
| tmtlv.setOUI(MacAddress.ONOS.oui()); |
| } |
| |
| public void setSig(byte[] sig) { |
| LLDPOrganizationalTLV sigtlv = opttlvs.get(SIG_SUBTYPE); |
| if (sigtlv == null) { |
| return; |
| } |
| sigtlv.setInfoString(sig); |
| sigtlv.setLength((short) (sig.length + SIG_LENGTH)); |
| sigtlv.setSubType(SIG_SUBTYPE); |
| sigtlv.setOUI(MacAddress.ONOS.oui()); |
| } |
| |
| public LLDPOrganizationalTLV getNameTLV() { |
| for (LLDPTLV tlv : this.getOptionalTLVList()) { |
| if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { |
| LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; |
| if (orgTLV.getSubType() == NAME_SUBTYPE) { |
| return orgTLV; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public LLDPOrganizationalTLV getDeviceTLV() { |
| for (LLDPTLV tlv : this.getOptionalTLVList()) { |
| if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { |
| LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; |
| if (orgTLV.getSubType() == DEVICE_SUBTYPE) { |
| return orgTLV; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public LLDPOrganizationalTLV getTimestampTLV() { |
| for (LLDPTLV tlv : this.getOptionalTLVList()) { |
| if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { |
| LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; |
| if (orgTLV.getSubType() == TIMESTAMP_SUBTYPE) { |
| return orgTLV; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public LLDPOrganizationalTLV getSigTLV() { |
| for (LLDPTLV tlv : this.getOptionalTLVList()) { |
| if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { |
| LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; |
| if (orgTLV.getSubType() == SIG_SUBTYPE) { |
| return orgTLV; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the TLV associated with remote probing. This TLV will be null if |
| * remote probing is disabled. |
| * |
| * @return A TLV containing domain ID, or null. |
| */ |
| public LLDPOrganizationalTLV getDomainTLV() { |
| for (LLDPTLV tlv : this.getOptionalTLVList()) { |
| if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { |
| LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; |
| if (orgTLV.getSubType() == DOMAIN_SUBTYPE) { |
| return orgTLV; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public LLDPTLV getPortDescTLV() { |
| for (LLDPTLV tlv : this.getOptionalTLVList()) { |
| if (tlv.getType() == PORT_DESC_TLV_TYPE) { |
| return tlv; |
| } |
| } |
| |
| log.error("Cannot find the port description tlv type."); |
| return null; |
| } |
| |
| public String getNameString() { |
| LLDPOrganizationalTLV tlv = getNameTLV(); |
| if (tlv != null) { |
| return new String(tlv.getInfoString(), StandardCharsets.UTF_8); |
| } |
| return null; |
| } |
| |
| public String getDeviceString() { |
| LLDPOrganizationalTLV tlv = getDeviceTLV(); |
| if (tlv != null) { |
| return new String(tlv.getInfoString(), StandardCharsets.UTF_8); |
| } |
| return null; |
| } |
| |
| public String getDomainString() { |
| LLDPOrganizationalTLV tlv = getDomainTLV(); |
| if (tlv != null) { |
| return new String(tlv.getInfoString(), StandardCharsets.UTF_8); |
| } |
| return null; |
| } |
| |
| public String getPortDescString() { |
| LLDPTLV tlv = getPortDescTLV(); |
| if (tlv != null) { |
| return new String(tlv.getValue(), StandardCharsets.UTF_8); |
| } |
| return null; |
| } |
| |
| public Integer getPort() { |
| ByteBuffer portBB = ByteBuffer.wrap(this.getPortId().getValue()); |
| byte type = portBB.get(); |
| |
| if (type == PORT_TLV_COMPONENT_SUBTYPE) { |
| return Integer.parseInt(new String(portBB.array(), |
| portBB.position(), portBB.remaining(), StandardCharsets.UTF_8)); |
| } else { |
| return -1; |
| } |
| } |
| |
| public String getPortNameString() { |
| ByteBuffer portBB = ByteBuffer.wrap(this.getPortId().getValue()); |
| byte type = portBB.get(); |
| |
| if (type == PORT_TLV_INTERFACE_NAME_SUBTYPE) { |
| return new String(portBB.array(), portBB.position(), portBB.remaining(), StandardCharsets.UTF_8); |
| } else { |
| return null; |
| } |
| } |
| |
| public MacAddress getChassisIdByMac() { |
| ByteBuffer portBB = ByteBuffer.wrap(this.getChassisId().getValue()); |
| byte type = portBB.get(); |
| |
| if (type == CHASSIS_TLV_SUBTYPE) { |
| byte[] bytes = new byte[portBB.remaining()]; |
| |
| System.arraycopy(portBB.array(), portBB.position(), bytes, 0, MacAddress.MAC_ADDRESS_LENGTH); |
| |
| return new MacAddress(bytes); |
| } else { |
| return MacAddress.NONE; |
| } |
| } |
| |
| public short getTtlBySeconds() { |
| ByteBuffer portBB = ByteBuffer.wrap(this.getTtl().getValue()); |
| |
| return portBB.getShort(); |
| } |
| |
| public long getTimestamp() { |
| LLDPOrganizationalTLV tlv = getTimestampTLV(); |
| if (tlv != null) { |
| ByteBuffer b = ByteBuffer.allocate(8).put(tlv.getInfoString()); |
| b.flip(); |
| return b.getLong(); |
| } |
| return 0; |
| } |
| |
| public byte[] getSig() { |
| LLDPOrganizationalTLV tlv = getSigTLV(); |
| if (tlv != null) { |
| return tlv.getInfoString(); |
| } |
| return null; |
| } |
| |
| /** |
| * Given an ethernet packet, determines if this is an LLDP from |
| * ONOS and returns the device the LLDP came from. |
| * @param eth an ethernet packet |
| * @return a the lldp packet or null |
| */ |
| public static ONOSLLDP parseONOSLLDP(Ethernet eth) { |
| if (eth.getEtherType() == Ethernet.TYPE_LLDP || |
| eth.getEtherType() == Ethernet.TYPE_BSN) { |
| ONOSLLDP onosLldp = new ONOSLLDP((LLDP) eth.getPayload()); |
| if (ONOSLLDP.DEFAULT_NAME.equals(onosLldp.getNameString())) { |
| return onosLldp; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Given an ethernet packet, returns the device the LLDP came from. |
| * @param eth an ethernet packet |
| * @return a the lldp packet or null |
| */ |
| public static ONOSLLDP parseLLDP(Ethernet eth) { |
| if (eth.getEtherType() == Ethernet.TYPE_LLDP || |
| eth.getEtherType() == Ethernet.TYPE_BSN) { |
| |
| return new ONOSLLDP((LLDP) eth.getPayload()); |
| } |
| |
| log.error("Packet is not the LLDP or BSN."); |
| return null; |
| } |
| |
| /** |
| * Creates a link probe for link discovery/verification. |
| * @deprecated since 1.15. Insecure, do not use. |
| * |
| * @param deviceId The device ID as a String |
| * @param chassisId The chassis ID of the device |
| * @param portNum Port number of port to send probe out of |
| * @return ONOSLLDP probe message |
| */ |
| @Deprecated |
| public static ONOSLLDP onosLLDP(String deviceId, ChassisId chassisId, int portNum) { |
| ONOSLLDP probe = new ONOSLLDP(NAME_SUBTYPE, DEVICE_SUBTYPE); |
| probe.setPortId(portNum); |
| probe.setDevice(deviceId); |
| probe.setChassisId(chassisId); |
| return probe; |
| } |
| |
| /** |
| * Creates a link probe for link discovery/verification. |
| * |
| * @param deviceId The device ID as a String |
| * @param chassisId The chassis ID of the device |
| * @param portNum Port number of port to send probe out of |
| * @param secret LLDP secret |
| * @return ONOSLLDP probe message |
| */ |
| public static ONOSLLDP onosSecureLLDP(String deviceId, ChassisId chassisId, int portNum, String secret) { |
| ONOSLLDP probe = null; |
| if (secret == null) { |
| probe = new ONOSLLDP(NAME_SUBTYPE, DEVICE_SUBTYPE); |
| } else { |
| probe = new ONOSLLDP(NAME_SUBTYPE, DEVICE_SUBTYPE, TIMESTAMP_SUBTYPE, SIG_SUBTYPE); |
| } |
| probe.setPortId(portNum); |
| probe.setDevice(deviceId); |
| probe.setChassisId(chassisId); |
| |
| if (secret != null) { |
| /* Secure Mode */ |
| long ts = System.currentTimeMillis(); |
| probe.setTimestamp(ts); |
| byte[] sig = createSig(deviceId, portNum, ts, secret); |
| if (sig == null) { |
| return null; |
| } |
| probe.setSig(sig); |
| sig = null; |
| } |
| return probe; |
| } |
| |
| /** |
| * Creates a link probe for link discovery/verification. |
| * @deprecated since 1.15. Insecure, do not use. |
| * |
| * @param deviceId The device ID as a String |
| * @param chassisId The chassis ID of the device |
| * @param portNum Port number of port to send probe out of |
| * @param portDesc Port description of port to send probe out of |
| * @return ONOSLLDP probe message |
| */ |
| @Deprecated |
| public static ONOSLLDP onosLLDP(String deviceId, ChassisId chassisId, int portNum, String portDesc) { |
| ONOSLLDP probe = onosLLDP(deviceId, chassisId, portNum); |
| addPortDesc(probe, portDesc); |
| return probe; |
| } |
| |
| /** |
| * Creates a link probe for link discovery/verification. |
| * |
| * @param deviceId The device ID as a String |
| * @param chassisId The chassis ID of the device |
| * @param portNum Port number of port to send probe out of |
| * @param portDesc Port description of port to send probe out of |
| * @param secret LLDP secret |
| * @return ONOSLLDP probe message |
| */ |
| public static ONOSLLDP onosSecureLLDP(String deviceId, ChassisId chassisId, int portNum, String portDesc, |
| String secret) { |
| ONOSLLDP probe = onosSecureLLDP(deviceId, chassisId, portNum, secret); |
| addPortDesc(probe, portDesc); |
| return probe; |
| } |
| |
| private static void addPortDesc(ONOSLLDP probe, String portDesc) { |
| if (portDesc != null && !portDesc.isEmpty()) { |
| byte[] bPortDesc = portDesc.getBytes(StandardCharsets.UTF_8); |
| |
| if (bPortDesc.length > LLDPTLV.MAX_LENGTH) { |
| bPortDesc = Arrays.copyOf(bPortDesc, LLDPTLV.MAX_LENGTH); |
| } |
| LLDPTLV portDescTlv = new LLDPTLV() |
| .setType(PORT_DESC_TLV_TYPE) |
| .setLength((short) bPortDesc.length) |
| .setValue(bPortDesc); |
| probe.addOptionalTLV(portDescTlv); |
| } |
| } |
| |
| private static byte[] createSig(String deviceId, int portNum, long timestamp, String secret) { |
| byte[] pnb = ByteBuffer.allocate(8).putLong(portNum).array(); |
| byte[] tmb = ByteBuffer.allocate(8).putLong(timestamp).array(); |
| |
| try { |
| SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); |
| Mac mac = Mac.getInstance("HmacSHA256"); |
| mac.init(signingKey); |
| mac.update(deviceId.getBytes()); |
| mac.update(pnb); |
| mac.update(tmb); |
| byte[] sig = mac.doFinal(); |
| return sig; |
| } catch (NoSuchAlgorithmException e) { |
| return null; |
| } catch (InvalidKeyException e) { |
| return null; |
| } |
| } |
| |
| private static boolean verifySig(byte[] sig, String deviceId, int portNum, long timestamp, String secret) { |
| byte[] nsig = createSig(deviceId, portNum, timestamp, secret); |
| if (nsig == null) { |
| return false; |
| } |
| |
| if (!ArrayUtils.isSameLength(nsig, sig)) { |
| return false; |
| } |
| |
| boolean fail = false; |
| for (int i = 0; i < nsig.length; i++) { |
| if (sig[i] != nsig[i]) { |
| fail = true; |
| } |
| } |
| if (fail) { |
| return false; |
| } |
| return true; |
| } |
| |
| public static boolean verify(ONOSLLDP probe, String secret, long maxDelay) { |
| if (secret == null) { |
| return true; |
| } |
| |
| String deviceId = probe.getDeviceString(); |
| int portNum = probe.getPort(); |
| long timestamp = probe.getTimestamp(); |
| byte[] sig = probe.getSig(); |
| |
| if (deviceId == null || sig == null) { |
| return false; |
| } |
| |
| if (timestamp + maxDelay <= System.currentTimeMillis() || |
| timestamp > System.currentTimeMillis()) { |
| return false; |
| } |
| |
| return verifySig(sig, deviceId, portNum, timestamp, secret); |
| } |
| |
| } |