Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

#Run

Config:

  • PC 1: 192.168.1.47
  • PC 2: 192.168.1.89
  • ClickControl handler port for both: 3333
  • Input request port 1234

Start click:

# ~/path_to_click/click -p 3333 ~/path_to_conf/pc1.click

Add answers:

$ export HOST=192.168.1.89
$ export PORT=3333
$ export HANDLER_NAME=h_map
$ export SHORT_NAME=DummyAnswer

$ ./add_handler_mapping.sh 'OOOO|AAAA'...
$ ./read_handler.sh

Do the same thing with for pc 2 then send requests:

$ echo -n $(python -c 'print("O"*4)') | netcat -vvv -u 192.168.1.47 1234
#!/bin/bash
###
# Uses openbsd variant of netcat
###
if [ $# -eq 0 ]; then
echo "[-] No matching to add..."
echo -e "usage\t$0 '<request|answer>' ['<request|answer>' ...]\n"
exit 1
fi
# Host of the control socket
host="${HOST:?}"
# Port number of the control socket
port=${PORT:?}
# The short element's name (e.g. _not_ flatten by click)
short_name="${SHORT_NAME:?}"
# The handler name of the element
handler_name="${HANDLER_NAME:?}"
# We wait the following number of seconds before adding the next entry
sleep_time=2
##
## Do not modify below
##
# extract full name from the element list
full_name=$(echo "READ list" | nc -q 1 ${host} ${port} | grep ${short_name})
if [ "x${full_name}" == "x" ]; then
echo "[-] No element named ${short_name} found!"
exit 1
fi
# list all handlers and see if we have it
handlers_list=$(echo "READ ${full_name}.handlers" | nc -q 1 ${host} ${port})
echo "$handlers_list" | grep -q ${handler_name}
if [ $? -ne 0 ]; then
echo "[-] No handler named ${handler_name} for ${full_name}"
exit 1
fi
# The real deal, send the write orders to the handler
for i in $@; do
echo -e "WRITE ${full_name}.${handler_name} ${i}\n" | nc -q 1 ${host} ${port}
sleep ${sleep_time}
done
#include <click/config.h>
#include <click/args.hh>
#include "DummyAnswer.hh"
#include "DummyProto.hh"
CLICK_DECLS
DummyAnswer::DummyAnswer() { };
DummyAnswer::~DummyAnswer() { };
Packet *DummyAnswer::simple_action(Packet *p) {
struct DummyProto *proto = (struct DummyProto*) p->data();
String s = String(proto->Data, strnlen(proto->Data, DUMMYPROTO_DATA_LEN));
String res = _msgs.get(s);
if (!res) {
click_chatter("DEBUG: No response for %s", s.printable().c_str());
p->kill();
return NULL;
}
int res_len = res.length();
res.append_fill('\0', DUMMYPROTO_DATA_LEN-res_len);
struct DummyProto resp;
resp.T = DUMMYPROTO_ANSWER;
resp.Len = DUMMYPROTO_LEN_B;
memcpy(resp.Data, res.c_str(), DUMMYPROTO_DATA_LEN);
WritablePacket *q = Packet::make(headroom, &resp, sizeof(DummyProto), 0);
p->kill();
return q;
}
enum { H_MAP };
/*
* Inject a (request, answer).
* The input string must be formated like this:
* request_string|answer_string
*/
int DummyAnswer::write_callback(const String &s, Element *e, void *vparam, ErrorHandler *errh) {
DummyAnswer *da = static_cast<DummyAnswer *>(e);
if ((intptr_t) vparam != H_MAP)
return 0;
if (s.length() > DUMMYPROTO_DATA_LEN*2 +1)
return 1;
int pos_delim = s.find_left('|', 0);
if (pos_delim == -1 || pos_delim == 0 || pos_delim == s.length())
return 1;
int pos_req = pos_delim > DUMMYPROTO_DATA_LEN ? DUMMYPROTO_DATA_LEN : pos_delim;
String req = s.substring(0, pos_req);
String ans = s.substring(pos_delim+1, s.length());
click_chatter("Adding: '%s' -> '%s'", req.printable().c_str(), ans.printable().c_str());
da->_msgs[req] = ans;
return 0;
}
String DummyAnswer::read_callback(Element *e, void *vparam) {
DummyAnswer *da = static_cast<DummyAnswer *>(e);
if ((intptr_t) vparam != H_MAP)
return "";
String res("");
res += "------ MSGS ------\n";
for (HashTable<String, String>::iterator it = da->_msgs.begin(); it; it++) {
res += it.key().printable();
res += " -> ";
res += it.value().printable();
res += "\n";
}
res += "---- END MSGS ----\n";
return res;
}
void DummyAnswer::add_handlers() {
add_read_handler("h_map", read_callback, H_MAP, Handler::CALM);
add_write_handler("h_map", write_callback, H_MAP);
}
CLICK_ENDDECLS
EXPORT_ELEMENT(DummyAnswer)
#ifndef CLICK_DUMMYANSWER__HH
#define CLICK_DUMMYANSWER__HH
#include <click/element.hh>
#include <click/hashtable.hh>
#include <clicknet/ether.h>
#include <clicknet/udp.h>
CLICK_DECLS
/*
=c
DummyAnswer()
=s
Generates a packet with a preformated answer to a DummyRequest packet.
*/
class DummyAnswer : public Element {
HashTable<String, String> _msgs;
static int write_callback(const String &s, Element *e, void *vparam, ErrorHandler *errh);
static String read_callback(Element *e, void *vparam);
int headroom = sizeof(click_ip) + sizeof(click_udp) + sizeof(click_ether);
public:
DummyAnswer();
~DummyAnswer();
const char *class_name() const { return "DummyAnswer"; }
const char *port_count() const { return "1/1"; }
const char *processing() const { return AGNOSTIC; }
Packet *simple_action(Packet *p);
void add_handlers();
};
CLICK_ENDDECLS
#endif
#include <click/config.h>
#include "DummyClassifier.hh"
#include "DummyProto.hh"
CLICK_DECLS
DummyClassifier::DummyClassifier() { };
DummyClassifier::~DummyClassifier() { };
void DummyClassifier::push(int, Packet *p) {
int out_port = 2; // Default output for junk
if (p->anno_u8(DUMMY_CLASSIFY_ANNO_OFFSET) == DUMMYPROTO_REQUEST)
out_port = 0;
else if (p->anno_u8(DUMMY_CLASSIFY_ANNO_OFFSET) == DUMMYPROTO_ANSWER)
out_port = 1;
output(out_port).push(p);
};
CLICK_ENDDECLS
EXPORT_ELEMENT(DummyClassifier)
#ifndef CLICK_DUMMYCLASSIFIER__HH
#define CLICK_DUMMYCLASSIFIER__HH
#include <click/element.hh>
CLICK_DECLS
/*
=c
DummyClassifier()
=s
Classify according to the type of a dummy packet, this type is set in
annotations.
=d
Request packets go to the first output port and Answer to the second, the rest
goes to the 3rd output port. The annotation is an unsigned int on 8 bits and
is positionned at DUMMY_CLASSIFY_ANNO_OFFSET of the annotations.
*/
class DummyClassifier : public Element {
public:
DummyClassifier();
~DummyClassifier();
const char *class_name() const { return "DummyClassifier"; }
const char *port_count() const { return "1/3"; }
const char *processing() const { return PUSH; }
void push(int, Packet *p);
};
CLICK_ENDDECLS
#endif
#include <click/config.h>
#include <click/args.hh>
#include "DummyLog.hh"
#include "DummyProto.hh"
CLICK_DECLS
DummyLog::DummyLog() : _timer((Element *) this) { };
DummyLog::~DummyLog() { };
int DummyLog::initialize(ErrorHandler *) {
_timer.initialize((Element *) this);
_timer.schedule_now();
return 0;
}
int DummyLog::configure(Vector<String> &conf, ErrorHandler *errh) {
if (Args(conf, this, errh).read_or_set("TICK", _tick, 5).complete() < 0)
return -1;
return 0;
}
void DummyLog::run_timer(Timer *t) {
assert(&_timer == t);
_timer.reschedule_after_sec(_tick);
if (_answers.empty()) {
click_chatter("LOG is empty");
return;
}
click_chatter("------ LOG ------");
int i = 0;
for (Vector<String>::iterator it = _answers.begin(); it && i < _answers.size(); it++, i++ )
click_chatter("[%d] %s", i, it->c_str());
click_chatter("---- END LOG ----");
}
Packet *DummyLog::simple_action(Packet *p) {
struct DummyProto *proto = (struct DummyProto*) p->data();
String s;
s += "{ Len: ";
s += String(proto->Len);
s += ", Data: '";
s += String(proto->Data, strnlen(proto->Data, DUMMYPROTO_DATA_LEN)).printable();
s += "' }";
_answers.push_back(s);
p->kill();
return NULL;
}
CLICK_ENDDECLS
EXPORT_ELEMENT(DummyLog)
#ifndef CLICK_DUMMYLOG__HH
#define CLICK_DUMMYLOG__HH
#include <click/element.hh>
#include <click/timer.hh>
CLICK_DECLS
/*
=c
DummyLog(TICK)
=s
Log the received answers and prints them periodically.
=d
Print the received answers every TICK seconds.
*/
class DummyLog : public Element {
Vector<String> _answers;
Timer _timer;
unsigned int _tick;
void run_timer(Timer *t);
public:
DummyLog();
~DummyLog();
const char *class_name() const { return "DummyLog"; }
const char *port_count() const { return "1/0"; }
const char *processing() const { return AGNOSTIC; }
Packet *simple_action(Packet *p);
int initialize(ErrorHandler *);
int configure(Vector<String> &conf, ErrorHandler *errh);
};
CLICK_ENDDECLS
#endif
#include <click/config.h>
#include <click/glue.hh>
#include "DummyPrint.hh"
#include "DummyProto.hh"
CLICK_DECLS
DummyPrint::DummyPrint() { };
DummyPrint::~DummyPrint() { };
Packet *DummyPrint::simple_action(Packet *p) {
struct DummyProto *proto = (DummyProto *) p->data();
uint8_t anno_val = DUMMYPROTO_ANSWER + 1;
if (proto->T == DUMMYPROTO_REQUEST) {
click_chatter("Request: %s", String(proto->Data, DUMMYPROTO_DATA_LEN).c_str());
anno_val = (uint8_t) DUMMYPROTO_REQUEST;
} else if (proto->T == DUMMYPROTO_ANSWER) {
click_chatter("Answer: %s", String(proto->Data, DUMMYPROTO_DATA_LEN).c_str());
anno_val = (uint8_t) DUMMYPROTO_ANSWER;
} else
click_chatter("ERROR: unknow type for packet");
p->set_anno_u8(DUMMY_CLASSIFY_ANNO_OFFSET, anno_val);
return p;
};
CLICK_ENDDECLS
EXPORT_ELEMENT(DummyPrint)
#ifndef CLICK_DUMMYPRINT__HH
#define CLICK_DUMMYPRINT__HH
#include <click/element.hh>
CLICK_DECLS
/*
=c
DummyPrint()
=s
Print the 'Data' field of the packet preposed with 'Answer: ' or 'Request: '
depending on the 'T' field.
=d
Sets an annotation of type uint8_t at DUMMY_CLASSIFY_ANNO_OFFSET to ease the
task of DummyClassifier.
*/
class DummyPrint : public Element {
public:
DummyPrint();
~DummyPrint();
const char *class_name() const { return "DummyPrint"; }
const char *port_count() const { return "1/1"; }
const char *processing() const { return AGNOSTIC; }
Packet *simple_action(Packet *p);
};
CLICK_ENDDECLS
#endif
#ifndef DUMMYPROTO_HH
#define DUMMYPROTO_HH
#define DUMMYPROTO_REQUEST 0
#define DUMMYPROTO_ANSWER 1
#define DUMMY_CLASSIFY_ANNO_OFFSET 4
#define DUMMYPROTO_DATA_LEN 100
#define DUMMYPROTO_LEN_A 1 << 0
#define DUMMYPROTO_LEN_B 1 << 1
struct DummyProto {
#if CLICK_BYTE_ORDER == CLICK_BIG_ENDIAN
unsigned int T : 1;
unsigned int Len : 7;
#elif CLICK_BYTE_ORDER == CLICK_LITTLE_ENDIAN
unsigned int Len : 7;
unsigned int T : 1;
#else
#error "Undefined Byte Order!"
#endif
char Data[DUMMYPROTO_DATA_LEN];
} CLICK_SIZE_PACKED_ATTRIBUTE;
#endif
#include <click/config.h>
#include "DummyRequest.hh"
#include "DummyProto.hh"
CLICK_DECLS
DummyRequest::DummyRequest() { };
DummyRequest::~DummyRequest() { };
/*
* Generates a DummyRequest packet from a input string.
*/
Packet *DummyRequest::gen_dummy_request(String s) {
struct DummyProto dummy_packet;
dummy_packet.T = DUMMYPROTO_REQUEST;
memcpy(dummy_packet.Data, s.c_str(), DUMMYPROTO_DATA_LEN);
dummy_packet.Len = DUMMYPROTO_LEN_A;
return Packet::make(headroom, &dummy_packet, sizeof(DummyProto), 0);
};
Packet *DummyRequest::simple_action(Packet *p) {
Packet *q = NULL;
int len = strnlen((const char *) p->data(), p->length());
if (len > DUMMYPROTO_DATA_LEN)
len = DUMMYPROTO_DATA_LEN;
String s = String(p->data(), len);
int delta = DUMMYPROTO_DATA_LEN - len;
if (delta > 0)
s.append_fill('\0', delta);
click_chatter("DEBUG: p->data() = %s\tp->length() = %d", s.c_str(), p->length());
if (p->length() > 0 && p->length() <= DUMMYPROTO_DATA_LEN + 1)
q = gen_dummy_request(s);
else
click_chatter("ERROR: Input packet is too big or 0-sized!");
p->kill();
return q;
};
CLICK_ENDDECLS
EXPORT_ELEMENT(DummyRequest)
#ifndef CLICK_DUMMYREQUEST__HH
#define CLICK_DUMMYREQUEST__HH
#include <click/element.hh>
#include <clicknet/ether.h>
#include <clicknet/udp.h>
CLICK_DECLS
/*
=c
DummyRequest()
=s
Generates a Dummy Request packet using another packet as input.
=d
The input packet data must be a valid C type string. Also the data pointer of
the packet must be positioned at the beginning of the string!
*/
class DummyRequest : public Element {
Packet *gen_dummy_request(String s);
int headroom = sizeof(click_ip) + sizeof(click_udp) + sizeof(click_ether);
public:
DummyRequest();
~DummyRequest();
const char *class_name() const { return "DummyRequest"; }
const char *port_count() const { return "1/1"; }
const char *processing() const { return AGNOSTIC; }
Packet *simple_action(Packet *p);
};
CLICK_ENDDECLS
#endif
define($IFACENAME enp2s0);
AddressInfo($IFACENAME 192.168.1.47 5c:f9:dd:4e:5f:81);
define($LOCALPORT 1234);
define($REMOTEPORT 4321);
define($REMOTEIP 192.168.1.89);
FromDevice($IFACENAME)
-> cl :: Classifier(12/0800, // IP packets
12/0806 20/0002, // ARP Replies
12/0806 20/0001 // ARP Queries
) // other
-> CheckIPHeader(14)
-> ip_cl :: IPClassifier(udp port $LOCALPORT, udp port $REMOTEPORT)
-> Strip(42)
-> DummyRequest
-> udpip_encap :: UDPIPEncap($IFACENAME, 4444, $REMOTEIP, $REMOTEPORT)
-> arpq :: ARPQuerier($IFACENAME)
-> out :: Queue
-> ToDevice($IFACENAME);
cl[1] -> [1]arpq;
cl[2] -> ARPResponder($IFACENAME) -> out;
arpq[1] -> out;
ip_cl[1]
-> Strip(42)
-> DummyPrint
-> dummy_cl :: DummyClassifier
-> DummyAnswer
-> udpip_encap;
dummy_cl[1] -> DummyLog;
dummy_cl[2] -> Discard;
define($IFACENAME enp0s25);
AddressInfo($IFACENAME 192.168.1.89 d8:9d:67:99:55:8e);
define($LOCALPORT 1234);
define($REMOTEPORT 4321);
define($REMOTEIP 192.168.1.47);
FromDevice($IFACENAME)
-> cl :: Classifier(12/0800, // IP packets
12/0806 20/0002, // ARP Replies
12/0806 20/0001 // ARP Queries
) // other
-> CheckIPHeader(14)
-> ip_cl :: IPClassifier(udp port $LOCALPORT, udp port $REMOTEPORT)
-> Strip(42)
-> DummyRequest
-> udpip_encap :: UDPIPEncap($IFACENAME, 4444, $REMOTEIP, $REMOTEPORT)
-> arpq :: ARPQuerier($IFACENAME)
-> out :: Queue
-> ToDevice($IFACENAME);
cl[1] -> [1]arpq;
cl[2] -> ARPResponder($IFACENAME) -> out;
arpq[1] -> out;
ip_cl[1]
-> Strip(42)
-> DummyPrint
-> dummy_cl :: DummyClassifier
-> DummyAnswer
-> udpip_encap;
dummy_cl[1] -> DummyLog;
dummy_cl[2] -> Discard;
#!/bin/bash
###
# Uses openbsd variant of netcat
###
# Host of the control socket
host="${HOST:?}"
# Port number of the control socket
port=${PORT:?}
# The short element's name (e.g. _not_ flatten by click)
short_name="${SHORT_NAME:?}"
# The handler name of the element
handler_name="${HANDLER_NAME:?}"
##
## Do not modify below
##
# extract full name from the element list
full_name=$(echo "READ list" | nc -q 1 ${host} ${port} | grep ${short_name})
if [ "x${full_name}" == "x" ]; then
echo "[-] No element named ${short_name} found!"
exit 1
fi
# list all handlers and see if we have it
handlers_list=$(echo "READ ${full_name}.handlers" | nc -q 1 ${host} ${port})
echo "$handlers_list" | grep -q ${handler_name}
if [ $? -ne 0 ]; then
echo "[-] No handler named ${handler_name} for ${full_name}"
exit 1
fi
# The real deal, send the read order to the handler
echo -e "READ ${full_name}.${handler_name}\n" | nc -q 1 ${host} ${port}
#!/bin/bash
function print_help() {
echo -e "\nUsage: $0 <class_name> <port_count> <processing>\n"
echo -e "\tclass_name\tMust be in the form : DummyElem (no spaces or special char)"
echo -e "\tport_count\tThe number of ports in the form <in>/<out> : 1/2"
echo -e "\tprocessing\tThe processing flow of the packets, one of : PUSH, PULL or AGNOSTIC"
}
if [ $# -ne 3 ]; then
print_help
exit 1
fi
elem_name=$1
elem_name_upper=${elem_name^^}
port_count=$2
processing=""
case $3 in
"PUSH" | "PULL" | "AGNOSTIC")
processing=$3
;;
*)
echo -n "\nERROR: '\$3\' isn't available, exiting\n"
print_help
exit 1
;;
esac
echo "INFO: creating ${elem_name}.hh"
cat << __EOF__ > ${elem_name}.hh
#ifndef CLICK_${elem_name_upper}__HH
#define CLICK_${elem_name_upper}__HH
#include <click/element.hh>
CLICK_DECLS
/*
=c
${elem_name}(TODO)
=s
TODO: Summary
=d
TODO: Complete description
*/
class ${elem_name} : public Element {
//TODO: Add private attributes
public:
${elem_name}();
~${elem_name}();
const char *class_name() const { return "${elem_name}"; }
const char *port_count() const { return "${port_count}"; }
const char *processing() const { return ${processing}; }
Packet *simple_action(Packet *p);
};
CLICK_ENDDECLS
#endif
__EOF__
echo "INFO: creating ${elem_name}.cc"
cat << __EOF__ > ${elem_name}.cc
#include <click/config.h>
// #include <click/TODO.hh>
#include "${elem_name}.hh"
#include "DummyProto.hh"
CLICK_DECLS
${elem_name}::${elem_name}() { };
${elem_name}::~${elem_name}() { };
Packet *${elem_name}::simple_action(Packet *p) {
// TODO: fill
return p;
};
CLICK_ENDDECLS
EXPORT_ELEMENT(${elem_name})
__EOF__
echo "INFO: finished!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.