/*
* Copyright (c) 2015, 2017 Ericsson, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.sfc.ofrenderer.openflow;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.Metadata;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The PacketIn rule will be triggered by the TransportEgress table when a TCP
* Proxy SF is being used. This class listens for IPv4 packets and will populate
* the PathMapperAcl table with 2 rules using the Packet's source/dest IP
* addresses: Rule 1: if(IpSrc == PacketInIpSrc AND IpDst == PacketInIpDst) then
* (set metadata to uplink RSP pathId and goto TransportEgress table) Rule 2:
* if(IpSrc == PacketInIpDst AND IpDst == PacketInIpSrc) then (set metadata to
* downlink RSP pathId and goto TransportEgress table)
*
* <p>
* Since a TCP Proxy SF will generate packets, the SFF wont know what to do with
* them unless we add the above rules. Upon receiving a TCP Syn from the client,
* the SF will establish a connection with the client (send TCP SynAck to
* client), and then establish a separate connection with the server (send TCP
* Syn to server).
*/
public class SfcIpv4PacketInHandler implements PacketProcessingListener, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(SfcIpv4PacketInHandler.class);
private static final int PACKET_OFFSET_ETHERTYPE = 12;
private static final int PACKET_OFFSET_IP = 14;
private static final int PACKET_OFFSET_IP_SRC = PACKET_OFFSET_IP + 12;
private static final int PACKET_OFFSET_IP_DST = PACKET_OFFSET_IP + 16;
public static final int ETHERTYPE_IPV4 = 0x0800;
private static final int DEFAULT_MAX_BUFFER_TIME = 60000; // 60 milliseconds
private static final int DEFAULT_PACKET_COUNT_PURGE = 100;
private final SfcOfFlowProgrammerImpl flowProgrammer;
private final Map<String, Long> pktInBuffer;
private int maxBufferTime;
private int packetCountPurge;
private int packetCount;
public SfcIpv4PacketInHandler(SfcOfFlowProgrammerImpl flowProgrammer) {
this.flowProgrammer = flowProgrammer;
pktInBuffer = new HashMap<>();
maxBufferTime = DEFAULT_MAX_BUFFER_TIME;
packetCountPurge = DEFAULT_PACKET_COUNT_PURGE;
packetCount = 0;
}
public int getMaxBufferTime() {
return maxBufferTime;
}
public void setMaxBufferTime(int maxBufferTime) {
this.maxBufferTime = maxBufferTime;
}
public int getPacketCountPurge() {
return packetCountPurge;
}
public void setPacketCountPurge(int packetCountPurge) {
this.packetCountPurge = packetCountPurge;
}
public int getBufferSize() {
return pktInBuffer.size();
}
/**
* The handler function for IPv4 PktIn packets.
*
* @param packetIn
* The incoming packet.
*/
@Override
public void onPacketReceived(PacketReceived packetIn) {
if (packetIn == null) {
return;
}
++packetCount;
if (packetCount > packetCountPurge) {
packetCount = 0;
purgePktInBuffer();
}
// Make sure the PacketIn is due to our Classification table pktInAction
if (!this.flowProgrammer.compareClassificationTableCookie(packetIn.getFlowCookie())) {
LOG.debug("SfcIpv4PacketInHandler discarding packet by Flow Cookie");
return;
}
// TODO figure out how to get the IDataPacketService which will parse
// the packet for us
final byte[] rawPacket = packetIn.getPayload();
// Get the EtherType and check that its an IP packet
if (getEtherType(rawPacket) != ETHERTYPE_IPV4) {
LOG.debug("SfcIpv4PacketInHandler discarding NON-IPv4");
return;
}
// Get the SrcIp and DstIp Addresses
String pktSrcIpStr = getSrcIpStr(rawPacket);
if (pktSrcIpStr == null) {
LOG.error("SfcIpv4PacketInHandler Cant get Src IP address, discarding packet");
return;
}
String pktDstIpStr = getDstIpStr(rawPacket);
if (pktDstIpStr == null) {
LOG.error("SfcIpv4PacketInHandler Cant get Src IP address, discarding packet");
return;
}
// Since all packets sent to SF are PktIn, only need to handle the first
// one
// In OpenFlow 1.5 we'll be able to do the PktIn on TCP Syn only
if (bufferPktIn(pktSrcIpStr, pktDstIpStr)) {
LOG.info("SfcIpv4PacketInHandler PacketIn buffered");
return;
}
LOG.info("SfcIpv4PacketInHandler PacketIn NOT buffered");
// Get the metadata
if (packetIn.getMatch() == null) {
LOG.error("SfcIpv4PacketInHandler Cant get packet flow match");
return;
}
if (packetIn.getMatch().getMetadata() == null) {
LOG.error("SfcIpv4PacketInHandler Cant get packet flow match metadata");
return;
}
Metadata pktMatchMetadata = packetIn.getMatch().getMetadata();
BigInteger metadata = pktMatchMetadata.getMetadata();
short ulPathId = metadata.shortValue();
// Assuming the RSP is symmetric
short dlPathId = (short) (ulPathId + 1);
LOG.info("SfcIpv4PacketInHandler Src IP [{}] Dst IP [{}] ulPathId [{}] dlPathId [{}]", pktSrcIpStr, pktDstIpStr,
ulPathId, dlPathId);
// Get the Node name, by getting the following
// - Ingress nodeConnectorRef
// - instanceID for the Node in the tree above us
// - instance identifier for the nodeConnectorRef
final String nodeName = packetIn.getIngress().getValue().firstKeyOf(Node.class, NodeKey.class).getId()
.getValue();
// Configure the uplink packet
if (ulPathId >= 0) {
this.flowProgrammer.setFlowRspId(new Long(ulPathId));
this.flowProgrammer.configurePathMapperAclFlow(nodeName, pktSrcIpStr, pktDstIpStr, ulPathId);
}
// Configure the downlink packet
if (dlPathId >= 0) {
this.flowProgrammer.setFlowRspId(new Long(dlPathId));
this.flowProgrammer.configurePathMapperAclFlow(nodeName, pktDstIpStr, pktSrcIpStr, dlPathId);
}
}
@Override
public void close() throws Exception {
}
/**
* Given a raw packet, return the EtherType.
*
* @param rawPacket packet
* @return etherType
*/
private short getEtherType(final byte[] rawPacket) {
final byte[] etherTypeBytes = Arrays.copyOfRange(rawPacket, PACKET_OFFSET_ETHERTYPE,
PACKET_OFFSET_ETHERTYPE + 2);
return packShort(etherTypeBytes);
}
/**
* Given a raw packet, return the SrcIp.
*
* @param rawPacket packet
* @return srcIp String
*/
private String getSrcIpStr(final byte[] rawPacket) {
final byte[] ipSrcBytes = Arrays.copyOfRange(rawPacket, PACKET_OFFSET_IP_SRC, PACKET_OFFSET_IP_SRC + 4);
String pktSrcIpStr = null;
try {
pktSrcIpStr = InetAddress.getByAddress(ipSrcBytes).getHostAddress();
} catch (UnknownHostException e) {
LOG.error("Exception getting Src IP address [{}]", e.getMessage(), e);
}
return pktSrcIpStr;
}
/**
* Given a raw packet, return the DstIp.
*
* @param rawPacket packet
* @return dstIp String
*/
private String getDstIpStr(final byte[] rawPacket) {
final byte[] ipDstBytes = Arrays.copyOfRange(rawPacket, PACKET_OFFSET_IP_DST, PACKET_OFFSET_IP_DST + 4);
String pktDstIpStr = null;
try {
pktDstIpStr = InetAddress.getByAddress(ipDstBytes).getHostAddress();
} catch (UnknownHostException e) {
LOG.error("Exception getting Dst IP address [{}]", e.getMessage(), e);
}
return pktDstIpStr;
}
/**
* Simple internal utility function to convert from a 2-byte array to a
* short.
*
* @param bytes byte array
* @return the bytes packed into a short
*/
private short packShort(byte[] bytes) {
short val = (short) 0;
for (int i = 0; i < 2; i++) {
val <<= 8;
val |= bytes[i] & 0xff;
}
return val;
}
/**
* Decide if packets with the same src/dst IP have already been processed.
* If they haven't been processed, store the IPs so they will be considered
* processed.
*
* @param srcIpStr source IP
* @param dstIpStr destination IP
* @return True if the src/dst IP has already been processed, False
* otherwise
*/
private boolean bufferPktIn(final String srcIpStr, final String dstIpStr) {
String key = new StringBuilder().append(srcIpStr).append(dstIpStr).toString();
long currentMillis = System.currentTimeMillis();
Long bufferedTime = pktInBuffer.get(key);
// If the entry does not exist, add it and return false indicating the
// packet needs to be processed
if (bufferedTime == null) {
// Add the entry
pktInBuffer.put(key, currentMillis);
return false;
}
// If the entry is old, update it and return false indicating the packet
// needs to be processed
if (currentMillis - bufferedTime.longValue() > maxBufferTime) {
// Update the entry
pktInBuffer.put(key, currentMillis);
return false;
}
return true;
}
/**
* Purge packets that have been in the PktIn buffer too long.
*/
private void purgePktInBuffer() {
long currentMillis = System.currentTimeMillis();
Set<String> keySet = pktInBuffer.keySet();
for (String key : keySet) {
Long bufferedTime = pktInBuffer.get(key);
if (currentMillis - bufferedTime.longValue() > maxBufferTime) {
// This also removes the entry from the pktInBuffer map and
// doesnt invalidate iteration
keySet.remove(key);
}
}
}
}