/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
* Copyright (c) 2015 xFlow Research 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.tsdr.netflow;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opendaylight.yang.gen.v1.opendaylight.tsdr.log.data.rev160325.tsdrlog.RecordAttributes;
import org.opendaylight.yang.gen.v1.opendaylight.tsdr.log.data.rev160325.tsdrlog.RecordAttributesBuilder;
/**
* @author <a href="mailto:saichler@gmail.com">Sharon Aicler</a>
* @author <a href="mailto:muhammad.umair@xflowresearch.com">Umair Bhatti</a>
*
* Modified: Jul 18, 2016
*/
public class NetflowPacketParser {
private final List<RecordAttributes> recordAttributes = new ArrayList<>();
private static HashMap<Integer, Integer> template;
private static enum netflowV9Attribs {
IN_BYTES,
IN_PKTS,
FLOWS,
PROTOCOL,
SRC_TOS,
TCP_FLAGS,
L4_SRC_PORT,
IPV4_SRC_ADDR,
SRC_MASK,
INPUT_SNMP,
L4_DST_PORT,
IPV4_DST_ADDR,
DST_MASK,
OUTPUT_SNMP,
IPV4_NEXT_HOP,
SRC_AS, DST_AS,
BGP_IPV4_NEXT_HOP,
MUL_DST_PKTS,
MUL_DST_BYTES,
LAST_SWITCHED,
FIRST_SWITCHED,
OUT_BYTES,
OUT_PKTS,
MIN_PKT_LNGTH,
MAX_PKT_LNGTH,
IPV6_SRC_ADDR,
IPV6_DST_ADDR,
IPV6_SRC_MASK,
IPV6_DST_MASK,
IPV6_FLOW_LABEL,
ICMP_TYPE,
MUL_IGMP_TYPE,
SAMPLING_INTERVAL,
SAMPLING_ALGORITHM,
FLOW_ACTIVE_TIMEOUT,
FLOW_INACTIVE_TIMEOUT,
ENGINE_TYPE,
ENGINE_ID,
TOTAL_BYTES_EXP,
TOTAL_PKTS_EXP,
TOTAL_FLOWS_EXP,
IPV4_SRC_PREFIX,
IPV4_DST_PREFIX,
MPLS_TOP_LABEL_TYPE,
MPLS_TOP_LABEL_IP_ADDR,
FLOW_SAMPLER_ID,
FLOW_SAMPLER_MODE,
FLOW_SAMPLER_RANDOM_INTERVAL,
MIN_TTL,
MAX_TTL,
IPV4_IDENT,
DST_TOS,
IN_SRC_MAC,
OUT_DST_MAC,
SRC_VLAN,
DST_VLAN,
IP_PROTOCOL_VERSION,
DIRECTION,
IPV6_NEXT_HOP,
BPG_IPV6_NEXT_HOP,
IPV6_OPTION_HEADERS,
MPLS_LABEL_1,
MPLS_LABEL_2,
MPLS_LABEL_3,
MPLS_LABEL_4,
MPLS_LABEL_5,
MPLS_LABEL_6,
MPLS_LABEL_7,
MPLS_LABEL_8,
MPLS_LABEL_9,
MPLS_LABEL_10,
IN_DST_MAC,
OUT_SRC_MAC,
IF_NAME,
IF_DESC,
SAMPLER_NAME,
IN_PERMANENT_BYTES,
IN_PERMANENT_PKTS
}
/*
* Constructor just make the header for netflow packet.There could be multiple PDU's of which the header would be same.
*/
public NetflowPacketParser(final byte[] buff){
template = null;
netflowV9Attribs.values();//initializing here for code coverage.
int version = Integer.parseInt(convert(buff, 0, 2));
if(version == 9){
addValue("version", "" + version + "");
addValue("sysUpTime",convert(buff, 4, 4));
addValue("unix_secs",convert(buff, 8, 4));
addValue("flow_sequence",convert(buff, 12, 4));
addValue("source_id",convert(buff, 16, 4));
}else{
addValue("version", "" + version + "");
addValue("sysUpTime",convert(buff, 4, 4));
addValue("unix_secs",convert(buff, 8, 4));
addValue("unix_nsecs",convert(buff, 12, 4));
addValue("flow_sequence",convert(buff, 16, 4));
addValue("engine_type",Byte.toString(buff[20]));
addValue("engine_id",Byte.toString(buff[21]));
long s_interval = convert(buff[23]);
s_interval += Long.parseLong(convert(buff, 23, 1));
addValue("samplingInterval","" + s_interval);
}
}
public static HashMap<Integer, Integer> getTemplate() {
return template;
}
/**
* function to add netflow format to the packets. The netflow header would be same while the format would be different according to the PDU's.
* @param buff - the byte array of data contained in netflow packet.
* @param len - the offset in the byte array where the data starts from.
*/
public void addFormat(byte[] buff, int len){
int version = Integer.parseInt((convert(buff, 0, 2)));
if(version == 9){
addFormatV9(buff, len);
}
else{
addFormatV5(buff, len);
}
}
public static void fillFlowSetTemplateMap(byte[] buff, int len, int count){
if(template == null){
template = new HashMap<Integer, Integer>();
if(buff == null){
return;
}
while(count > 0){
int attrib = Integer.parseInt(convert(buff, len, 2));
int lenn = Integer.parseInt(convert(buff, len + 2, 2));
template.put(attrib, lenn);
len += 4;
count -= 1;
}
}
}
public void addFormatV9(byte[] buff, int len){
if(template != null){
netflowV9Attribs[] attributes = netflowV9Attribs.values();
int dataOffset = 0;
for(Map.Entry<Integer, Integer> entry : template.entrySet()){
int attribID = entry.getKey();
int attribLen = entry.getValue();
addValue(attributes[attribID].toString(),convert(buff, len + dataOffset, attribLen));
dataOffset += attribLen;
}
}else{
addValue("startTime",convert(buff, len, 4));
addValue("endTime", convert(buff, len+4, 4));
addValue("Octets",convert(buff, len+8, 4));
addValue("Packets",convert(buff, len+12, 4));
addValue("inputInt", convert(buff, len+16, 2));
addValue("outputInt", convert(buff, len+18, 2));
addValue("srcAddr", convertIPAddress(new Long(convert(buff, len + 20, 4)).longValue()));
addValue("dstAddr",convertIPAddress(new Long(convert(buff, len + 24, 4)).longValue()));
addValue("Protocol", convert(buff, len+28, 1));
addValue("ipTOS", convert(buff, len+29, 1));
addValue("srcPort", convert(buff, len+30, 2));
addValue("dstPort", convert(buff, len+32, 2));
addValue("samplerID", convert(buff, len+34, 1));
addValue("flowClass", convert(buff, len+35, 1));
addValue("nextHop", convertIPAddress(new Long(convert(buff, len + 36, 4)).longValue()));
addValue("dstMask", convert(buff, len+40, 1));
addValue("srcMask", convert(buff, len+41, 1));
addValue("TCPFlags", convert(buff, len+42, 1));
addValue("Direction", convert(buff, len+43, 1));
addValue("dstAS", convert(buff, len+44, 2));
addValue("srcAS", convert(buff, len+46, 2));
}
}
public void addFormatV5(byte[] buff, int len){
addValue("srcAddr",convertIPAddress(new Long(convert(buff, len+24, 4)).longValue()));
addValue("dstAddr",convertIPAddress(new Long(convert(buff, len+28, 4)).longValue()));
addValue("nextHop",convertIPAddress(new Long(convert(buff, len+32, 4)).longValue()));
addValue("input",convert(buff, len+36, 2));
addValue("output", convert(buff, len+38, 2));
addValue("dPkts", convert(buff, len+40, 4));
addValue("dOctets", convert(buff, len+44, 4));
String first = convert(buff, len+48, 4);
addValue("First", first);
String last = convert(buff, len+52, 4);
addValue("Last",last);
addValue("srcPort",convert(buff, len+56, 2));
addValue("dstPort",convert(buff, len + 58, 2));
addValue("tcpFlags",Byte.toString(buff[len+61]));
addValue("protocol",Byte.toString(buff[len+62]));
addValue("tos", Byte.toString(buff[len+63]));
addValue("srcAS",convert(buff, len+64, 2));
addValue("dstAS",convert(buff, len+66, 2));
addValue("srcMask",Byte.toString(buff[len+68]));
addValue("dstMask",Byte.toString(buff[len+69]));
addValue("flowDuration",new Long(Long.parseLong(last) - Long.parseLong(first)).toString());
}
public List<RecordAttributes> getRecordAttributes(){
return this.recordAttributes;
}
public void addValue(String name,String value){
RecordAttributesBuilder builder = new RecordAttributesBuilder();
builder.setName(name);
builder.setValue(value);
this.recordAttributes.add(builder.build());
}
/**
* function to convert the IP address from byte to decimal (quad dotted) notation
* @param addr1 - long representing the ip address. if this is ipv4 it should be int and not long, ipv6 is two longs not one.
* @return - String of the ip address
*/
public static final String convertIPAddress(long addr1){
int addr = (int) (addr1 & 0xffffffff);
StringBuffer buf = new StringBuffer();
buf.append(((addr >>> 24) & 0xff)).append('.').append(((addr >>> 16) & 0xff)).append('.').append(((addr >>> 8) & 0xff)).append('.').append(addr & 0xff);
return buf.toString();
}
/**
* function to convert attributes from byte data to long data type accordingly.
* @param p - the byte array containing the long
* @param off - The offet place where the long starts
* @param len - The length, actually we should remove this parameter as a long is always 8 bytes.
* @return - A string representation of the long
*/
public static final String convert(byte[] p, int off, int len){
long ret = 0;
int done = off + len;
for (int i = off; i < done; i++){
ret = ((ret << 8) & 0xffffffff) + (p[i] & 0xff);
}
return (new Long(ret)).toString();
}
/**
* function to convert the sampling interval (6 bits of 23rd byte)
* @param p - byte out of 6 bits representing the interval.
* @return - long interval
*/
public static final long convert(byte p){
long ret = 0;
ret = ((ret << 8) & 0xffffffff) + (p & 0x3f);
return ret;
}
public String toString(){
StringBuilder sb = new StringBuilder();
boolean first = true;
for(RecordAttributes ra:this.recordAttributes){
if(!first){
sb.append(",");
}
sb.append(ra.getName());
sb.append("=");
sb.append(ra.getValue());
first = false;
}
return sb.toString();
}
}