L3/L4 Firewall (simple_firewall)¶
Introduction¶
The simple_firewall
program is a sample L3/L4 firewall which looks at
network addresses and ports of a TCP packet and determines if that packet
should be allowed or blocked.
The following code block shows the example rules for simple_firewall
.
Each rule consists of action, source address, destination address, and
(optional) port fields. The action field can be either ACCEPT or DROP.
The source and destination address field can be any network address or
netmask space on which the action will be performed. The port field
specifies the TCP port number relevant to the action.
#(act) (src) (dst) (port)
DROP 10.0.0.0/24 10.0.0.0/24 dport:80
ACCEPT 10.0.1.7 10.0.1.9 sport:1024
ACCEPT 10.0.2.7 10.0.2.9
ACCEPT 10.0.3.0/24 10.0.3.0/24
Code Walkthrough¶
The following sections provide an explanation of the main components of the
simple_firewall code.
All mOS net library functions used in the sample code are prefixed with mtcp_
and are explained in detail in the Programmer’s Guide - mOS Programming API.
Note that we omit the error handling logics in the example codes for brevity.
(1) The main() Function¶
The main()
function performs the initialization and calls the execution
threads for each CPU core.
The first task is to initialize mOS thread based on the mOS configuration file.
mos_conf_file
is the file path to the mos.conf
file which will be
provided to mtcp_init()
. ParseConfigFile()
function is a custom parser
for simple_firewall
rules whose format is specified in the previous subsection.
/* parse mos configuration file */
ret = mtcp_init(mos_conf_file);
/* parse simple firewall-specfic startup file */
ParseConfigFile(simple_firewall_file);
Within the main()
function, you can define your own events (user-defined
event) which can be used by the application. Following example defines
an event triggered when the incoming packet is an initial SYN packet
(not a SYN+ACK packet) for a certain flow.
/* event for the initial SYN packet */
initSYNEvent = mtcp_define_event(MOS_ON_PKT_IN, CatchInitSYN);
static bool
CatchInitSYN(mctx_t mctx, int sockid,
int side, event_t events, filter_arg_t *arg)
{
struct pkt_info p;
mtcp_getlastpkt(mctx, sockid, side, &p);
return (p.tcph->syn && !p.tcph->ack);
}
We use mtcp_getconf()
to retrieve the mOS configuration parameters (e.g. number
of cores).
/* populate local mos-specific mcfg struct for later usage */
mtcp_getconf(&mcfg);
...
/* initialize monitor threads */
for (i = 0; i < mcfg.num_cores; i++)
CreateAndInitThreadContext(&ctx[i], i, initSYNEvent);
(2) Per-thread Initialization Function¶
The CreateAndInitThreadContext()
function is the core functional part
of the mOS thread initialization. core
variable is the CPU id
that is used to affinitize new thread to the core. udeForSYN
is
the user-defined event which catches the initial SYN packet for each
flow.
static void
CreateAndInitThreadContext(struct thread_context* ctx, int core, event_t udeForSYN)
{
...
The first step is the creation of mtcp context and socket. This is followed
by the creation of a MOS_SOCK_MONITOR_STREAM
socket to monitor TCP-related
events.
/* create mtcp context */
ctx->mctx = mtcp_create_context(core);
/* create socket */
ctx->mon_listener = mtcp_socket(ctx->mctx, AF_INET, MOS_SOCK_MONITOR_STREAM, 0);
The next step is to register the event callback function, which handles the incoming
packets with specific conditions. This simple_firewall
application registers an event
callback function for handling the initial SYN packet.
/* register callback */
mtcp_register_callback(ctx->mctx, ctx->mon_listener, udeForSYN,
MOS_PRE_RCV, ApplyActionPerFlow);
The last step of the per-thread initialization is to register timer callback for printing the firewall rule table every second. Note that this function should be registered only for CPU core id 0, to prevent duplicate printing.
/* CPU 0 is in charge of printing stats */
if (ctx->mctx->cpu == 0 &&
mtcp_settimer(ctx->mctx, ctx->mon_listener, &tv_1sec, DumpFWRuleTable))
...
(3) The ApplyActionPerFlow()
Event Callback¶
The ApplyActionPerFlow()
event callback function is used for applying actions for each
initial SYN packet of the flows. The following code snippet shows the procedure of
retrieving the packet information and looking up the firewall rules. mtcp_getlastpkt()
function retrieves the packet information in the variable p
, and the application
looks up for the action for that flow (defined by the network address and port number).
mtcp_getlastpkt(mctx, msock, side, &p);
...
action = FWRLookup(p.iph->saddr, p.iph->daddr, p.tcph->source, p.tcph->dest);
If the decided action is to drop the packet, the application calls mtcp_setlastpkt()
with MOS_DROP
keyword. If the decided action is to accept the flow, the application
does not need to monitor that flow anymore, and it can stop monitoring the flow by
calling mtcp_setsockopt()
with MOS_STOP_MON
keyword.
if (action == FRA_DROP) {
mtcp_setlastpkt(mctx, msock, side, 0, NULL, 0, MOS_DROP);
} else {
assert(action == FRA_ACCEPT);
/* no need to monitor this flow any more */
opt = MOS_SIDE_BOTH;
mtcp_setsockopt(mctx, msock, SOL_MONSOCKET,
MOS_STOP_MON, &opt, sizeof(opt));
}