/** * Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University * * 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.floodlightcontroller.packet; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.projectfloodlight.openflow.types.EthType; import org.projectfloodlight.openflow.types.MacAddress; import org.projectfloodlight.openflow.types.VlanVid; /** * * @author David Erickson (daviderickson@cs.stanford.edu) */ public class Ethernet extends BasePacket { private static String HEXES = "0123456789ABCDEF"; public static final short TYPE_ARP = 0x0806; public static final short TYPE_RARP = (short) 0x8035; public static final short TYPE_IPv4 = 0x0800; public static final short TYPE_IPv6 = (short) 0x86DD; public static final short TYPE_LLDP = (short) 0x88cc; public static final short TYPE_BSN = (short) 0x8942; public static final short VLAN_UNTAGGED = VlanVid.ZERO.getVlan(); // untagged vlan must be 0x0000 for loxi. We can use the convenient ZERO field public static final short DATALAYER_ADDRESS_LENGTH = 6; // bytes public static Map<Short, Class<? extends IPacket>> etherTypeClassMap; static { etherTypeClassMap = new HashMap<Short, Class<? extends IPacket>>(); etherTypeClassMap.put(TYPE_ARP, ARP.class); etherTypeClassMap.put(TYPE_RARP, ARP.class); etherTypeClassMap.put(TYPE_IPv4, IPv4.class); etherTypeClassMap.put(TYPE_IPv6, IPv6.class); etherTypeClassMap.put(TYPE_LLDP, LLDP.class); etherTypeClassMap.put(TYPE_BSN, BSN.class); } protected MacAddress destinationMACAddress; protected MacAddress sourceMACAddress; protected byte priorityCode; protected short vlanID; protected EthType etherType; protected boolean pad = false; /** * By default, set Ethernet to untagged */ public Ethernet() { super(); this.vlanID = VLAN_UNTAGGED; } /** * @return the destination MAC */ public MacAddress getDestinationMACAddress() { return destinationMACAddress; } /** * @param destinationMACAddress the destination MAC to set */ public Ethernet setDestinationMACAddress(byte[] destinationMACAddress) { this.destinationMACAddress = MacAddress.of(destinationMACAddress); return this; } /** * @param destinationMACAddress the destination MAC to set */ public Ethernet setDestinationMACAddress(MacAddress destinationMACAddress) { this.destinationMACAddress = destinationMACAddress; return this; } /** * @param destinationMACAddress the destination MAC to set */ public Ethernet setDestinationMACAddress(String destinationMACAddress) { this.destinationMACAddress = MacAddress.of(destinationMACAddress); return this; } /** * @return the source MACAddress */ public MacAddress getSourceMACAddress() { return sourceMACAddress; } /** * @param sourceMACAddress the source MAC to set */ public Ethernet setSourceMACAddress(byte[] sourceMACAddress) { this.sourceMACAddress = MacAddress.of(sourceMACAddress); return this; } /** * @param sourceMACAddress the source MAC to set */ public Ethernet setSourceMACAddress(MacAddress sourceMACAddress) { this.sourceMACAddress = sourceMACAddress; return this; } /** * @param sourceMACAddress the source MAC to set */ public Ethernet setSourceMACAddress(String sourceMACAddress) { this.sourceMACAddress = MacAddress.of(sourceMACAddress); return this; } /** * @return the priorityCode */ public byte getPriorityCode() { return priorityCode; } /** * @param priorityCode the priorityCode to set */ public Ethernet setPriorityCode(byte priorityCode) { this.priorityCode = priorityCode; return this; } /** * @return the vlanID */ public short getVlanID() { return vlanID; } /** * @param vlanID the vlanID to set */ public Ethernet setVlanID(short vlanID) { this.vlanID = vlanID; return this; } /** * @return the etherType */ public EthType getEtherType() { return etherType; } /** * @param etherType the etherType to set */ public Ethernet setEtherType(EthType etherType) { this.etherType = etherType; return this; } /** * @return True if the Ethernet frame is broadcast, false otherwise */ public boolean isBroadcast() { assert(destinationMACAddress.getLength() == 6); return destinationMACAddress.isBroadcast(); } /** * @return True is the Ethernet frame is multicast, False otherwise */ public boolean isMulticast() { return destinationMACAddress.isMulticast(); } /** * Pad this packet to 60 bytes minimum, filling with zeros? * @return the pad */ public boolean isPad() { return pad; } /** * Pad this packet to 60 bytes minimum, filling with zeros? * @param pad the pad to set */ public Ethernet setPad(boolean pad) { this.pad = pad; return this; } public byte[] serialize() { byte[] payloadData = null; if (payload != null) { payload.setParent(this); payloadData = payload.serialize(); } int length = 14 + ((vlanID == VLAN_UNTAGGED) ? 0 : 4) + ((payloadData == null) ? 0 : payloadData.length); if (pad && length < 60) { length = 60; } byte[] data = new byte[length]; ByteBuffer bb = ByteBuffer.wrap(data); bb.put(destinationMACAddress.getBytes()); bb.put(sourceMACAddress.getBytes()); if (vlanID != VLAN_UNTAGGED) { bb.putShort((short) EthType.VLAN_FRAME.getValue()); bb.putShort((short) ((priorityCode << 13) | (vlanID & 0x0fff))); } bb.putShort((short) etherType.getValue()); if (payloadData != null) bb.put(payloadData); if (pad) { Arrays.fill(data, bb.position(), data.length, (byte)0x0); } return data; } @Override public IPacket deserialize(byte[] data, int offset, int length) { if (length <= 16) // Ethernet packet minimum should be 60, this is reasonable return null; ByteBuffer bb = ByteBuffer.wrap(data, offset, length); if (this.destinationMACAddress == null) this.destinationMACAddress = MacAddress.of(new byte[6]); byte[] dstAddr = new byte[MacAddress.NONE.getLength()]; bb.get(dstAddr); this.destinationMACAddress = MacAddress.of(dstAddr); if (this.sourceMACAddress == null) this.sourceMACAddress = MacAddress.of(new byte[6]); byte[] srcAddr = new byte[MacAddress.NONE.getLength()]; bb.get(srcAddr); this.sourceMACAddress = MacAddress.of(srcAddr); /* * The ethertype is represented as 2 bytes in the packet header; * however, EthType can only parse an int. Negative shorts (1 in * the most sig place b/c 2's complement) are still valid ethertypes, * but it doesn't appear this way unless we treat the sign bit as * part of an unsigned number. If we natively cast the short to an * integer, it will sign extend into the extra bytes when in fact * it should still be a positive number. We can bitmask to force the * upper bytes to remain 0's. TODO this should be incorporated into * loxigen. */ EthType etherType = EthType.of(bb.getShort() & 0xffff); if (etherType == EthType.VLAN_FRAME) { /* In loxigen, EthType will give the same objects for a given ethertype --> shallow compare. */ short tci = bb.getShort(); this.priorityCode = (byte) ((tci >> 13) & 0x07); this.vlanID = (short) (tci & 0x0fff); etherType = EthType.of(bb.getShort() & 0xffff); } else { this.vlanID = VLAN_UNTAGGED; } this.etherType = etherType; IPacket payload; if (Ethernet.etherTypeClassMap.containsKey((short) this.etherType.getValue())) { Class<? extends IPacket> clazz = Ethernet.etherTypeClassMap.get((short) this.etherType.getValue()); try { payload = clazz.newInstance(); this.payload = payload.deserialize(data, bb.position(), bb.limit() - bb.position()); } catch (PacketParsingException e) { if (log.isTraceEnabled()) { log.trace("Failed to parse ethernet packet {}->{}" + " payload as {}, treat as plain ethernet packet", new Object[] {this.sourceMACAddress, this.destinationMACAddress, clazz.getClass().getName()}); log.trace("Exception from parsing {}", e); } this.payload = new Data(data); } catch (InstantiationException e) { if (log.isTraceEnabled()) { log.trace("Fail to instantiate class {}, {}", clazz.getClass().getName(), e); } this.payload = new Data(data); } catch (IllegalAccessException e) { if (log.isTraceEnabled()) { log.trace("Fail to access class for instantiation {}, {}", clazz.getClass().getName(), e); } this.payload = new Data(data); } catch (RuntimeException e) { if (log.isTraceEnabled()) { log.trace("Runtime exception during packet parsing {}", e); } this.payload = new Data(data); } } else { byte[] buf = new byte[bb.remaining()]; bb.get(buf); this.payload = new Data(buf); } this.payload.setParent(this); return this; } /** * Checks to see if a string is a valid MAC address. * @param macAddress * @return True if macAddress is a valid MAC, False otherwise */ public static boolean isMACAddress(String macAddress) { String[] macBytes = macAddress.split(":"); if (macBytes.length != 6) return false; for (int i = 0; i < 6; ++i) { if (HEXES.indexOf(macBytes[i].toUpperCase().charAt(0)) == -1 || HEXES.indexOf(macBytes[i].toUpperCase().charAt(1)) == -1) { return false; } } return true; } /** * Accepts a MAC address of the form 00:aa:11:bb:22:cc, case does not * matter, and returns a corresponding byte[]. * @param macAddress The MAC address to convert into a bye array * @return The macAddress as a byte array */ public static byte[] toMACAddress(String macAddress) { return MacAddress.of(macAddress).getBytes(); } /** * Accepts a MAC address and returns the corresponding long, where the * MAC bytes are set on the lower order bytes of the long. * @param macAddress * @return a long containing the mac address bytes */ public static long toLong(byte[] macAddress) { return MacAddress.of(macAddress).getLong(); } /** * Convert a long MAC address to a byte array * @param macAddress * @return the bytes of the mac address */ public static byte[] toByteArray(long macAddress) { return MacAddress.of(macAddress).getBytes(); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((destinationMACAddress == null) ? 0 : destinationMACAddress .hashCode()); result = prime * result + ((etherType == null) ? 0 : etherType.hashCode()); result = prime * result + (pad ? 1231 : 1237); result = prime * result + priorityCode; result = prime * result + ((sourceMACAddress == null) ? 0 : sourceMACAddress.hashCode()); result = prime * result + vlanID; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; Ethernet other = (Ethernet) obj; if (destinationMACAddress == null) { if (other.destinationMACAddress != null) return false; } else if (!destinationMACAddress.equals(other.destinationMACAddress)) return false; if (etherType == null) { if (other.etherType != null) return false; } else if (!etherType.equals(other.etherType)) return false; if (pad != other.pad) return false; if (priorityCode != other.priorityCode) return false; if (sourceMACAddress == null) { if (other.sourceMACAddress != null) return false; } else if (!sourceMACAddress.equals(other.sourceMACAddress)) return false; if (vlanID != other.vlanID) return false; return true; } /* (non-Javadoc) * @see java.lang.Object#toString(java.lang.Object) */ @Override public String toString() { StringBuffer sb = new StringBuffer("\n"); IPacket pkt = this.getPayload(); if (pkt instanceof ARP) sb.append("arp"); else if (pkt instanceof LLDP) sb.append("lldp"); else if (pkt instanceof ICMP) sb.append("icmp"); else if (pkt instanceof IPv4) sb.append("ip"); else if (pkt instanceof DHCP) sb.append("dhcp"); else sb.append(this.getEtherType().toString()); sb.append("\ndl_vlan: "); if (this.getVlanID() == Ethernet.VLAN_UNTAGGED) sb.append("untagged"); else sb.append(this.getVlanID()); sb.append("\ndl_vlan_pcp: "); sb.append(this.getPriorityCode()); sb.append("\ndl_src: "); sb.append(this.getSourceMACAddress().toString()); sb.append("\ndl_dst: "); sb.append(this.getDestinationMACAddress().toString()); if (pkt instanceof ARP) { ARP p = (ARP) pkt; sb.append("\nnw_src: "); sb.append(p.getSenderProtocolAddress().toString()); sb.append("\nnw_dst: "); sb.append(p.getTargetProtocolAddress().toString()); } else if (pkt instanceof LLDP) { sb.append("lldp packet"); } else if (pkt instanceof ICMP) { ICMP icmp = (ICMP) pkt; sb.append("\nicmp_type: "); sb.append(icmp.getIcmpType()); sb.append("\nicmp_code: "); sb.append(icmp.getIcmpCode()); } else if (pkt instanceof IPv4) { IPv4 p = (IPv4) pkt; sb.append("\nnw_src: "); sb.append(p.getSourceAddress().toString()); sb.append("\nnw_dst: "); sb.append(p.getDestinationAddress().toString()); sb.append("\nnw_tos: "); sb.append(p.getDiffServ()); sb.append("\nnw_proto: "); sb.append(p.getProtocol()); } else if (pkt instanceof IPv6) { IPv6 p = (IPv6) pkt; sb.append("\nnw_src: "); sb.append(p.getSourceAddress().toString()); sb.append("\nnw_dst: "); sb.append(p.getDestinationAddress().toString()); sb.append("\nnw_tclass: "); sb.append(p.getTrafficClass()); sb.append("\nnw_proto: "); sb.append(p.getNextHeader().toString()); } else if (pkt instanceof DHCP) { sb.append("\ndhcp packet"); } else if (pkt instanceof Data) { sb.append("\ndata packet"); } else if (pkt instanceof LLC) { sb.append("\nllc packet"); } else if (pkt instanceof BPDU) { sb.append("\nbpdu packet"); } else sb.append("\nunknown packet"); return sb.toString(); } }