Anti TCP SYN Port Scan

[topology]

H1--- S1 -----H3

H2---

 

H1 will use TCP SYN to do the port scan. S1(p4 switch) will detect it and block the packet sent from H1.

Detect method: when the number of SYN packets - the number of SYN+ACK packets > 3, then S1 will block the connection.

 

[basic.p4]

/* -*- P4_16 -*- */

#include <core.p4>

#include <v1model.p4>

const bit<16> TYPE_IPV4 = 0x800;

 

/*************************************************************************

*********************** H E A D E R S  ***********************************

*************************************************************************/

typedef bit<9>  egressSpec_t;

typedef bit<48> macAddr_t;

typedef bit<32> ip4Addr_t;

 

register<bit<10>>(1024) syn_cnt;

register<bit<10>>(1024) syn_ack_cnt;

 

header ethernet_t {

    macAddr_t dstAddr;

    macAddr_t srcAddr;

    bit<16>   etherType;

}

 

header ipv4_t {

    bit<4>    version;

    bit<4>    ihl;

    bit<8>    diffserv;

    bit<16>   totalLen;

    bit<16>   identification;

    bit<3>    flags;

    bit<13>   fragOffset;

    bit<8>    ttl;

    bit<8>    protocol;

    bit<16>   hdrChecksum;

    ip4Addr_t srcAddr;

    ip4Addr_t dstAddr;

}

 

header tcp_t {

    bit<16> srcPort;

    bit<16> dstPort;

    bit<32> seqNo;

    bit<32> ackNo;

    bit<4>  dataOffset;

    bit<4>  res;

    bit<8>  flags;

    bit<16> window;

    bit<16> checksum;

    bit<16> urgentPtr;

}

 

header udp_t {

    bit<16> srcPort;

    bit<16> dstPort;

    bit<16> udplength;

    bit<16> checksum;

}

 

struct metadata {

    bit<10>   flowlet_map_index;

    bit<10>    syn_count;

    bit<10>    syn_ack_count;

}

 

struct headers {

    ethernet_t   ethernet;

    ipv4_t       ipv4;

    tcp_t        tcp;

    udp_t       udp;

}

 

/*************************************************************************

*********************** P A R S E R  ***********************************

*************************************************************************/

parser MyParser(packet_in packet,

                out headers hdr,

                inout metadata meta,

                inout standard_metadata_t standard_metadata) {

 

    state start {

        transition parse_ethernet;

    }

 

    state parse_ethernet {

        packet.extract(hdr.ethernet);

        transition select(hdr.ethernet.etherType) {

            TYPE_IPV4: parse_ipv4;

            default: accept;

        }

    }

 

    state parse_ipv4 {

        packet.extract(hdr.ipv4);

        transition select(hdr.ipv4.protocol) {

            0x06: parse_tcp;

            0x11: parse_udp;

            default: accept;

        }

    }

 

    state parse_tcp {

        packet.extract(hdr.tcp);

        transition accept; 

    }

 

    state parse_udp {

        packet.extract(hdr.udp);

        transition accept; 

    }

}

 

/*************************************************************************

************   C H E C K S U M    V E R I F I C A T I O N   *************

*************************************************************************/

control MyVerifyChecksum(inout headers hdr, inout metadata meta) { 

    apply {  }

}

 

/*************************************************************************

**************  I N G R E S S   P R O C E S S I N G   *******************

*************************************************************************/

control MyIngress(inout headers hdr,

                  inout metadata meta,

                  inout standard_metadata_t standard_metadata) {

 

    action add_syn_cnt() {

        hash(meta.flowlet_map_index, HashAlgorithm.crc16, (bit<16>)0, { hdr.ipv4.srcAddr }, (bit<32>)1024);

        syn_cnt.read(meta.syn_count, (bit<32>)meta.flowlet_map_index);

        meta.syn_count=meta.syn_count+1;

        syn_cnt.write((bit<32>)meta.flowlet_map_index, meta.syn_count);

    }

 

    action add_syn_ack_cnt() {

        hash(meta.flowlet_map_index, HashAlgorithm.crc16, (bit<16>)0, { hdr.ipv4.dstAddr }, (bit<32>)1024);

        syn_ack_cnt.read(meta.syn_ack_count, (bit<32>)meta.flowlet_map_index);

        meta.syn_ack_count=meta.syn_ack_count+1;

        syn_ack_cnt.write((bit<32>)meta.flowlet_map_index, meta.syn_ack_count);

    } 

 

    action drop() {

        mark_to_drop(standard_metadata);

    }

 

    action forward(macAddr_t dstAddr, egressSpec_t port) {

        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;

        hdr.ethernet.dstAddr = dstAddr;

        standard_metadata.egress_spec = port;

        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;

    }

 

    table ip_forward {

        key = {

            hdr.ipv4.dstAddr: exact;

        }

        actions = {

            forward;

            drop;

        }

        size = 1024;

        default_action = drop();

    }

 

    apply {

        bit<1> set_drop=0;

        if (hdr.tcp.isValid()){

           if(hdr.tcp.flags==2) {

             add_syn_cnt();

    

             hash(meta.flowlet_map_index, HashAlgorithm.crc16, (bit<16>)0, { hdr.ipv4.srcAddr }, (bit<32>)1024);

             bit<10> tmp;

             syn_ack_cnt.read(tmp, (bit<32>)meta.flowlet_map_index);

             if(tmp==0 && meta.syn_count>3){

               set_drop=1;

             }

             if (tmp!=0 && meta.syn_count > (bit<10>)(3+tmp)){

               set_drop=1; 

             }

           } else if (hdr.tcp.flags==0x12) {

             add_syn_ack_cnt();

           }

        }

 

        if( hdr.ipv4.isValid() && set_drop==0){

             ip_forward.apply();

        }

    }

}

 

/*************************************************************************

****************  E G R E S S   P R O C E S S I N G   *******************

*************************************************************************/

control MyEgress(inout headers hdr,

                 inout metadata meta,

                 inout standard_metadata_t standard_metadata) {

    apply {  }

}

 

/*************************************************************************

*************   C H E C K S U M    C O M P U T A T I O N   **************

*************************************************************************/

control MyComputeChecksum(inout headers  hdr, inout metadata meta) {

     apply {

        update_checksum(

            hdr.ipv4.isValid(),

            { hdr.ipv4.version,

              hdr.ipv4.ihl,

              hdr.ipv4.diffserv,

              hdr.ipv4.totalLen,

              hdr.ipv4.identification,

              hdr.ipv4.flags,

              hdr.ipv4.fragOffset,

              hdr.ipv4.ttl,

              hdr.ipv4.protocol,

              hdr.ipv4.srcAddr,

              hdr.ipv4.dstAddr },

            hdr.ipv4.hdrChecksum,

            HashAlgorithm.csum16);

    }

}

 

/*************************************************************************

***********************  D E P A R S E R  *******************************

*************************************************************************/

control MyDeparser(packet_out packet, in headers hdr) {

    apply {

        packet.emit(hdr.ethernet);

        packet.emit(hdr.ipv4);

        packet.emit(hdr.tcp);

        packet.emit(hdr.udp);

    }

}

 

/*************************************************************************

***********************  S W I T C H  *******************************

*************************************************************************/

V1Switch(

MyParser(),

MyVerifyChecksum(),

MyIngress(),

MyEgress(),

MyComputeChecksum(),

MyDeparser()

) main;

 

[p4app.json]

{

  "program": "basic.p4",

  "switch": "simple_switch",

  "compiler": "p4c",

  "options": "--target bmv2 --arch v1model --std p4-16",

  "switch_cli": "simple_switch_CLI",

  "cli": true,

  "pcap_dump": true,

  "enable_log": true,

  "topo_module": {

    "file_path": "",

    "module_name": "p4utils.mininetlib.apptopo",

    "object_name": "AppTopoStrategies"

  },

  "controller_module": null,

  "topodb_module": {

    "file_path": "",

    "module_name": "p4utils.utils.topology",

    "object_name": "Topology"

  },

  "mininet_module": {

    "file_path": "",

    "module_name": "p4utils.mininetlib.p4net",

    "object_name": "P4Mininet"

  },

  "topology": {

    "assignment_strategy": "manual",

    "auto_arp_tables": "true",

    "auto_gw_arp": "true",

    "links": [["h1", "s1"], ["h2", "s1"],["h3", "s1"]],

    "hosts": {

      "h1": {

        "ip": "10.0.1.1/24",

        "gw": "10.0.1.254"

      },

      "h2": {

        "ip": "10.0.2.1/24",

       "gw": "10.0.2.254"

      },

      "h3": {

        "ip": "10.0.3.1/24",

       "gw": "10.0.3.254"

      }  

    },

    "switches": {

      "s1": {

        "cli_input": "cmd.txt",

        "program": "basic.p4"

      }

    }

  }

}

 

[cmd.txt]

table_add ip_forward forward 10.0.1.1 => 00:00:0a:00:01:01 1

table_add ip_forward forward 10.0.2.1 => 00:00:0a:00:02:01 2

table_add ip_forward forward 10.0.3.1 => 00:00:0a:00:03:01 3

 

[execution]

(modified @2020/6/11)

The detection is moved from p4 to controller

[basic.p4]

#include <core.p4>

#include <v1model.p4>

typedef bit<48> macAddr_t;

typedef bit<9> egressSpec_t;

 

header arp_t {

    bit<16> htype;

    bit<16> ptype;

    bit<8>  hlen;

    bit<8>  plen;

    bit<16> opcode;

    bit<48> hwSrcAddr;

    bit<32> protoSrcAddr;

    bit<48> hwDstAddr;

    bit<32> protoDstAddr;

}

 

header ethernet_t {

    bit<48> dstAddr;

    bit<48> srcAddr;

    bit<16> etherType;

}

 

header ipv4_t {

    bit<4>  version;

    bit<4>  ihl;

    bit<8>  diffserv;

    bit<16> totalLen;

    bit<16> identification;

    bit<3>  flags;

    bit<13> fragOffset;

    bit<8>  ttl;

    bit<8>  protocol;

    bit<16> hdrChecksum;

    bit<32> srcAddr;

    bit<32> dstAddr;

}

 

struct metadata {

}

 

struct headers {

    @name(".arp")

    arp_t      arp;

    @name(".ethernet")

    ethernet_t ethernet;

    @name(".ipv4")

    ipv4_t     ipv4;

}

 

parser ParserImpl(packet_in packet, out headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    @name(".parse_arp") state parse_arp {

        packet.extract(hdr.arp);

        transition accept;

    }

    @name(".parse_ethernet") state parse_ethernet {

        packet.extract(hdr.ethernet);

        transition select(hdr.ethernet.etherType) {

            16w0x800: parse_ipv4;

            16w0x806: parse_arp;

            default: accept;

        }

    }

    @name(".parse_ipv4") state parse_ipv4 {

        packet.extract(hdr.ipv4);

        transition accept;

    }

    @name(".start") state start {

        transition parse_ethernet;

    }

}

 

control egress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

 

    action tocpu() {

        clone(CloneType.E2E,100);

    }

  

    table send2cpu {

        actions = {

            tocpu;

            NoAction;

        }

        key = {

            hdr.ipv4.srcAddr: lpm;

        }

        size = 512;

        const default_action = NoAction;

    }      

    apply {

      if(hdr.ipv4.isValid() && standard_metadata.instance_type == 0){

          send2cpu.apply();

        }

    }

}

 

control ingress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) {

    action forward(macAddr_t dstAddr, egressSpec_t port) {

        //set the src mac address as the previous dst, this is not correct right?

        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;

 

        //set the destination mac address that we got from the match in the table

        hdr.ethernet.dstAddr = dstAddr;

 

        //set the output port that we also get from the table

        standard_metadata.egress_spec = port;

 

        //decrease ttl by 1

        hdr.ipv4.ttl = hdr.ipv4.ttl - 1;

    }

    action _drop() {

        mark_to_drop(standard_metadata);

    }

    table ipv4_lpm {

        actions = {

            forward;

            _drop;

        }

        key = {

            hdr.ipv4.dstAddr: exact;

        }

        size = 512;

        const default_action = _drop();

    }

    table block_pkt {

       actions = {

             NoAction;

             _drop;

        }

        key = {

            hdr.ipv4.srcAddr: exact;

        }

        size = 512;

        const default_action =  NoAction();

    }      

    apply {

        ipv4_lpm.apply();

        block_pkt.apply();

    }

}

 

control DeparserImpl(packet_out packet, in headers hdr) {

    apply {

        packet.emit(hdr.ethernet);

        packet.emit(hdr.arp);

        packet.emit(hdr.ipv4);

    }

}

 

control verifyChecksum(inout headers hdr, inout metadata meta) {

    apply {

        verify_checksum(true, { hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr }, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);

    }

}

 

control computeChecksum(inout headers hdr, inout metadata meta) {

    apply {

        update_checksum(true, { hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr }, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);

    }

}

 

V1Switch(ParserImpl(), verifyChecksum(), ingress(), egress(), computeChecksum(), DeparserImpl()) main;

 

[p4app.json]

{

  "program": "basic.p4",

  "switch": "simple_switch",

  "compiler": "p4c",

  "options": "--target bmv2 --arch v1model --std p4-16",

  "switch_cli": "simple_switch_CLI",

  "cli": true,

  "pcap_dump": true,

  "enable_log": true,

  "topo_module": {

    "file_path": "",

    "module_name": "p4utils.mininetlib.apptopo",

    "object_name": "AppTopoStrategies"

  },

  "controller_module": null,

  "topodb_module": {

    "file_path": "",

    "module_name": "p4utils.utils.topology",

    "object_name": "Topology"

  },

  "mininet_module": {

    "file_path": "",

    "module_name": "p4utils.mininetlib.p4net",

    "object_name": "P4Mininet"

  },

  "topology": {

    "assignment_strategy": "manual",

    "auto_arp_tables": "true",

    "auto_gw_arp": "true",

    "links": [["h1", "s1"], ["h2", "s1"],["h3", "s1"]],

    "hosts": {

      "h1": {

        "ip": "10.0.1.1/24",

        "gw": "10.0.1.254"

      },

      "h2": {

        "ip": "10.0.2.1/24",

       "gw": "10.0.2.254"

      },

      "h3": {

        "ip": "10.0.3.1/24",

       "gw": "10.0.3.254"

      }  

    },

    "switches": {

      "s1": {

        "cli_input": "cmd.txt",

        "program": "basic.p4",

        "cpu_port": true

      }

    }

  }

}

 

[cmd.txt]

table_add ipv4_lpm forward 10.0.1.1 => 00:00:0a:00:01:01 1

table_add ipv4_lpm forward 10.0.2.1 => 00:00:0a:00:02:01 2

table_add ipv4_lpm forward 10.0.3.1 => 00:00:0a:00:03:01 3

#table_add block_pkt _drop 10.0.1.1 =>

table_add send2cpu tocpu 10.0.1.0/24 =>

table_add send2cpu tocpu 10.0.2.0/24 =>

table_add send2cpu tocpu 10.0.3.0/24 =>

mirroring_add 100 4

 

[controller.py]

import nnpy

import struct

from p4utils.utils.topology import Topology

from p4utils.utils.sswitch_API import SimpleSwitchAPI

from scapy.all import Ether, sniff, Packet, TCP

from collections import Counter

count1= Counter()

count2= Counter()

val1=0

val2=0

blockip=[]

 

class myController(object):

    def __init__(self):

        self.topo = Topology(db="topology.db")

        self.controllers = {}

        self.connect_to_switches()

 

    def connect_to_switches(self):

        for p4switch in self.topo.get_p4switches():

            thrift_port = self.topo.get_thrift_port(p4switch)

            #print "p4switch:", p4switch, "thrift_port:", thrift_port

            self.controllers[p4switch] = SimpleSwitchAPI(thrift_port)  

 

    def recv_msg_cpu(self, pkt):

        print "interface:", pkt.sniffed_on

        print "summary:", pkt.summary()

        global val1,val2

 

        if TCP in pkt and pkt[TCP].flags==2:

          src = pkt.sprintf('{IP:%IP.src%}')

          dst = pkt.sprintf('{IP:%IP.dst%}')

          count1[(src, dst)] += 1

          val1=count1[(src, dst)]

          print "count1[",src,",",dst,"]=",count1[(src, dst)]

        if TCP in pkt and pkt[TCP].flags==18:

          src = pkt.sprintf('{IP:%IP.src%}')

          dst = pkt.sprintf('{IP:%IP.dst%}')

          count2[(dst, src)] += 1

          val2=count2[(dst, src)]

          print "count2[",dst,",",src,"]=",count2[(dst, src)]

       

        print "val1:", val1, " val2:", val2

        if (val1-val2>=3) and (TCP in pkt) and pkt[TCP].flags==2:

           src = pkt.sprintf('{IP:%IP.src%}')

           if src not in blockip:

             self.controllers["s1"].table_add("block_pkt", "_drop", [str(src)], [])

             blockip.append(src)

          

    

    def run_cpu_port_loop(self):

        cpu_interfaces = [str(self.topo.get_cpu_port_intf(sw_name).replace("eth0", "eth1")) for sw_name in self.controllers]

        sniff(iface=cpu_interfaces, prn=self.recv_msg_cpu)

      

if __name__ == "__main__":

    controller = myController()

    controller.run_cpu_port_loop()

 

[execution]

 

Open another terminal for running controller program.

 

H1 ping h3 test.

 

H1 starts to use nc to do port scan for h3. But the controller detects it. Then the controller sends a rule to the p4 switch to block h1.

 

H1 ping h3 again. We can see that h1 can no responses from h3. Because the packets sent from h1 are blocked and dropped.

 

[references]

https://github.com/sangh42/Port-Scan-Detector

https://myapollo.com.tw/zh-tw/linux-command-nc/

http://n.sfs.tw/content/index/10505

 

Dr. Chih-Heng Ke (smallko@gmail.com)

Department of Computer Science and Information Engineering,

National Quemoy University, Kinmen, Taiwan.