469 lines
14 KiB
Plaintext
469 lines
14 KiB
Plaintext
/* netfilter connection tracking testsuite in TTCN-3
|
|
* (C) 2017 by Harald Welte <laforge@gnumonks.org>
|
|
* Licensed under GNU GPLv2 */
|
|
module NFCT_testsuite {
|
|
|
|
import from General_Types all;
|
|
import from IP_Types all;
|
|
import from UDP_Types all;
|
|
import from CommonDefs all;
|
|
|
|
import from TunDevice_PortType all;
|
|
import from TunDevice_Types all;
|
|
|
|
import from NetfilterConntrack_Functions all;
|
|
import from http_www_netfilter_org_xml_libnetfilter_conntrack all;
|
|
import from XSD all;
|
|
|
|
modulepar charstring left_ip := "1.1.1.200";
|
|
modulepar charstring left_ip6 := "1::200";
|
|
|
|
modulepar charstring right_ip := "2.2.2.200";
|
|
modulepar charstring right_ip6 := "2::200";
|
|
|
|
type component dummy_CT {
|
|
port TunDevice_PT TUN;
|
|
port TunDevice_PT TUN2;
|
|
var boolean initialized := false;
|
|
timer T := 3.0;
|
|
}
|
|
|
|
template (value) Tun_send tunmsg(octetstring p_data) := { msg := p_data };
|
|
template (value) Tun_recv tunrcv(octetstring p_data) := { msg := p_data };
|
|
|
|
import from UsefulTtcn3Types all;
|
|
|
|
type record pkt_info {
|
|
nfct_direction direction,
|
|
octetstring payload,
|
|
NonNegativeInteger trunc_len,
|
|
boolean exp_pass
|
|
}
|
|
|
|
/* generate a flow_info using pre-defined default addresses + * incremented port */
|
|
private function flow_gen(integer port_delta, unsignedbyte l3_prot := 4, unsignedbyte l4_prot := c_ip_proto_udp) return flow_info {
|
|
var flow_info flowi := {
|
|
l3_protocol := c_ip_proto_ipv4,
|
|
src_ip := left_ip,
|
|
dst_ip := right_ip,
|
|
l4_protocol := l4_prot,
|
|
src_port := 1000 + port_delta,
|
|
dst_port := 2000 + port_delta
|
|
}
|
|
if (l3_prot == 6) {
|
|
flowi.l3_protocol := c_ip_proto_ipv6;
|
|
flowi.src_ip := left_ip6;
|
|
flowi.dst_ip := right_ip6;
|
|
}
|
|
return flowi
|
|
}
|
|
|
|
private function get_random_port_offset() return unsignedshort {
|
|
var float r := rnd();
|
|
return float2int(r * (65535.0 - 2000.0));
|
|
}
|
|
|
|
private function get_random_id() return unsignedshort {
|
|
var float r := rnd();
|
|
return float2int(r * 65535.0);
|
|
}
|
|
|
|
template IPv4_header t_ipv4h_normal(LIN1 l4_proto, OCT4 saddr, OCT4 daddr) := {
|
|
ver := c_ip_version_ipv4,
|
|
hlen := 5,
|
|
tos := 0,
|
|
tlen := 0,
|
|
id := get_random_id(),
|
|
res := '0'B,
|
|
dfrag := '0'B,
|
|
mfrag := '0'B,
|
|
foffset := 0,
|
|
ttl := 128,
|
|
proto := l4_proto,
|
|
cksum := 0,
|
|
srcaddr := saddr,
|
|
dstaddr := daddr
|
|
}
|
|
|
|
template IPv6_header t_ipv6h_normal(LIN1 l4_proto, OCT16 saddr, OCT16 daddr, NonNegativeInteger plen) := {
|
|
ver := c_ip_version_ipv6,
|
|
trclass := 0,
|
|
flabel := 0,
|
|
plen := plen,
|
|
nexthead := l4_proto,
|
|
hlim := 128,
|
|
srcaddr := saddr,
|
|
dstaddr := daddr
|
|
}
|
|
|
|
/* generate a payload (L5+) */
|
|
private function flow_gen_payload(flow_info flowi, pkt_info pkti) return octetstring {
|
|
var integer random_offset := float2int(rnd()*256.0);
|
|
return substr(tsc_RandomPRBS, random_offset, 232);
|
|
}
|
|
|
|
/* generate a UDP pseudo-header */
|
|
private function flow_gen_udp_pseudo_hdr4(flow_info flowi, pkt_info pkti, integer plen) return UDP_pseudo_header {
|
|
var UDP_pseudo_header ret := {
|
|
ipv4 := {
|
|
zero := 0,
|
|
proto := c_ip_proto_udp,
|
|
plen := plen
|
|
}
|
|
};
|
|
|
|
if (pkti.direction == DIR_ORIG) {
|
|
ret.ipv4.srcaddr := f_IPv4_addr_enc(flowi.src_ip);
|
|
ret.ipv4.dstaddr := f_IPv4_addr_enc(flowi.dst_ip);
|
|
} else {
|
|
ret.ipv4.srcaddr := f_IPv4_addr_enc(flowi.dst_ip);
|
|
ret.ipv4.dstaddr := f_IPv4_addr_enc(flowi.src_ip);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private function flow_gen_udp_pseudo_hdr6(flow_info flowi, pkt_info pkti, integer plen) return UDP_pseudo_header {
|
|
var UDP_pseudo_header ret := {
|
|
ipv6 := {
|
|
plen := plen,
|
|
zero := 0,
|
|
nextheader := c_ip_proto_udp
|
|
}
|
|
};
|
|
|
|
if (pkti.direction == DIR_ORIG) {
|
|
ret.ipv6.srcaddr := f_IPv6_addr_enc(flowi.src_ip);
|
|
ret.ipv6.dstaddr := f_IPv6_addr_enc(flowi.dst_ip);
|
|
} else {
|
|
ret.ipv6.srcaddr := f_IPv6_addr_enc(flowi.dst_ip);
|
|
ret.ipv6.dstaddr := f_IPv6_addr_enc(flowi.src_ip);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private function flow_gen_udp_pseudo_hdr(flow_info flowi, pkt_info pkti, integer plen) return UDP_pseudo_header {
|
|
if (flowi.l3_protocol == c_ip_proto_ipv4) {
|
|
return flow_gen_udp_pseudo_hdr4(flowi, pkti, plen);
|
|
} else if (flowi.l3_protocol == c_ip_proto_ipv6) {
|
|
return flow_gen_udp_pseudo_hdr6(flowi, pkti, plen);
|
|
} else {
|
|
testcase.stop("unknown/unsupported L3 Protocol");
|
|
}
|
|
}
|
|
|
|
/* generate a L4 packet according to flowi + pkti */
|
|
private function flow_gen_l4_pkt(flow_info flowi, pkt_info pkti) return octetstring {
|
|
var octetstring payload := flow_gen_payload(flowi, pkti);
|
|
var octetstring data;
|
|
var OCT2 csum;
|
|
if (flowi.l4_protocol == c_ip_proto_udp) {
|
|
var UDP_packet udp := {
|
|
header := {
|
|
len := lengthof(payload),
|
|
cksum := 0
|
|
},
|
|
payload := payload
|
|
};
|
|
if (pkti.direction == DIR_ORIG) {
|
|
udp.header.srcport := flowi.src_port;
|
|
udp.header.dstport := flowi.dst_port;
|
|
} else {
|
|
udp.header.srcport := flowi.dst_port;
|
|
udp.header.dstport := flowi.src_port;
|
|
}
|
|
data := f_UDP_enc(udp);
|
|
csum := f_UDP_checksum(f_UDP_pseudo_header_enc(flow_gen_udp_pseudo_hdr(flowi, pkti, lengthof(data))) & data);
|
|
data[6] := csum[0];
|
|
data[7] := csum[1];
|
|
} else {
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/* Encode IPv4 packet + compute checksum */
|
|
function f_IPv4_enc_csum(IPv4_packet ip) return octetstring {
|
|
var octetstring data;
|
|
var OCT2 csum;
|
|
data := f_IPv4_enc(ip);
|
|
csum := f_IPv4_checksum(data);
|
|
data[10] := csum[0];
|
|
data[11] := csum[1];
|
|
return data;
|
|
}
|
|
|
|
/* generate an IPv4 packet according to flowi + pkti */
|
|
function flow_gen_ipv4_pkt(flow_info flowi, pkt_info pkti) return octetstring {
|
|
var charstring src_ip, dst_ip;
|
|
|
|
if (pkti.direction == DIR_ORIG) {
|
|
src_ip := flowi.src_ip;
|
|
dst_ip := flowi.dst_ip;
|
|
} else {
|
|
src_ip := flowi.dst_ip;
|
|
dst_ip := flowi.src_ip;
|
|
}
|
|
|
|
var IPv4_packet ip := {
|
|
header := valueof(t_ipv4h_normal(flowi.l4_protocol,
|
|
f_IPv4_addr_enc(src_ip),
|
|
f_IPv4_addr_enc(dst_ip))),
|
|
ext_headers := omit,
|
|
payload := flow_gen_l4_pkt(flowi, pkti)
|
|
}
|
|
return f_IPv4_enc_csum(ip);
|
|
}
|
|
|
|
/* generate an IPv6 packet according to flowi + pkti */
|
|
function flow_gen_ipv6_pkt(flow_info flowi, pkt_info pkti) return octetstring {
|
|
var octetstring payload := flow_gen_l4_pkt(flowi, pkti);
|
|
var charstring src_ip, dst_ip;
|
|
|
|
if (pkti.direction == DIR_ORIG) {
|
|
src_ip := flowi.src_ip;
|
|
dst_ip := flowi.dst_ip;
|
|
} else {
|
|
src_ip := flowi.dst_ip;
|
|
dst_ip := flowi.src_ip;
|
|
}
|
|
|
|
var IPv6_packet ip := {
|
|
header := valueof(t_ipv6h_normal(flowi.l4_protocol, f_IPv4_addr_enc(src_ip), f_IPv4_addr_enc(dst_ip), lengthof(payload))),
|
|
payload := payload
|
|
}
|
|
return f_IPv6_enc(ip);
|
|
}
|
|
|
|
/* generate a packet according to the input flow + pkt information */
|
|
function flow_gen_pkt(flow_info flowi, pkt_info pkti) return octetstring {
|
|
var octetstring data;
|
|
if (flowi.l3_protocol == c_ip_proto_ipv4) {
|
|
data := flow_gen_ipv4_pkt(flowi, pkti);
|
|
} else if (flowi.l3_protocol == c_ip_proto_ipv6) {
|
|
data := flow_gen_ipv6_pkt(flowi, pkti);
|
|
} else {
|
|
data := ''O;
|
|
}
|
|
if (pkti.trunc_len > 0 and pkti.trunc_len < lengthof(data)) {
|
|
data := substr(data, 0, pkti.trunc_len);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/* decrement the TTL/hopcount of an IPv4/IPv6 packet */
|
|
function pkt_dec_hopcount(in octetstring pkt) return octetstring {
|
|
/* first decode the packet, decrease TTL, re-encode */
|
|
if (match(oct2hex(pkt[0]), '4?'H)) {
|
|
var IPv4_packet ip4 := f_IPv4_dec(pkt);
|
|
ip4.header.ttl := ip4.header.ttl - 1;
|
|
ip4.header.cksum := 0;
|
|
pkt := f_IPv4_enc_csum(ip4);
|
|
} else {
|
|
var IPv6_packet ip6 := f_IPv6_dec(pkt);
|
|
ip6.header.hlim := ip6.header.hlim -1;
|
|
pkt := f_IPv6_enc(ip6);
|
|
}
|
|
return pkt;
|
|
}
|
|
|
|
/* generate + send packet for given flow through TUN */
|
|
function flow_send_pkt_tun1(flow_info flowi, pkt_info pkti) runs on dummy_CT {
|
|
var octetstring pkt := flow_gen_pkt(flowi, pkti);
|
|
TUN.send(tunmsg(pkt));
|
|
if (pkti.exp_pass) {
|
|
T.start;
|
|
var octetstring exp_pkt := pkt_dec_hopcount(pkt);
|
|
log("expecting on TUN2", exp_pkt);
|
|
alt {
|
|
[] TUN2.receive(tunrcv(exp_pkt)) { }
|
|
[] TUN2.receive { log("unexpected receive"); repeat };
|
|
[] T.timeout { setverdict(inconc) }
|
|
}
|
|
T.stop;
|
|
}
|
|
}
|
|
|
|
/* generate + send packet for given flow through TUN2 */
|
|
function flow_send_pkt_tun2(flow_info flowi, pkt_info pkti) runs on dummy_CT {
|
|
var octetstring pkt := flow_gen_pkt(flowi, pkti);
|
|
TUN2.send(tunmsg(pkt));
|
|
if (pkti.exp_pass) {
|
|
T.start;
|
|
var octetstring exp_pkt := pkt_dec_hopcount(pkt);
|
|
log("expecting on TUN: ", exp_pkt);
|
|
alt {
|
|
[] TUN.receive(tunrcv(exp_pkt)) { }
|
|
[] TUN.receive { log("unexpected"); repeat }
|
|
[] T.timeout { setverdict(inconc) }
|
|
}
|
|
T.stop;
|
|
}
|
|
}
|
|
|
|
/* get nf_conntrack from kernel for given flow and match against template */
|
|
function get_nfct_and_match(flow_info flowi, template Flow t_flow) return boolean {
|
|
var Flow ct := f_get_conntrack(flowi);
|
|
log("conntrack found: ", ct);
|
|
var boolean ret := match(ct, t_flow);
|
|
if (not ret) {
|
|
setverdict(fail);
|
|
log("conntrack as read from kernel:", ct);
|
|
log("template that didn't match:", t_flow);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* get nf_conntrack from kernel for given flow and ensure there is none */
|
|
function get_nfct_ensure_none(flow_info flowi) return boolean {
|
|
var Flows cts := f_get_conntracks(flowi);
|
|
var boolean ret := false;
|
|
if (lengthof(cts.flow_list) == 0) {
|
|
ret := true;
|
|
}
|
|
if (not ret) {
|
|
log("conntrack found but expected none:", cts);
|
|
setverdict(fail);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* apply a certain tolerance of up to 1 second in the timeout * retrieved after the packet traversed conntrack */
|
|
function timeout_range(NonNegativeInteger secs) return template NonNegativeInteger {
|
|
return (secs-1 .. secs);
|
|
}
|
|
|
|
/* update Flow template tiemout with timeout range of (tout-1 .. tout) */
|
|
function tflow_set_timeout(inout template Flow tflow, NonNegativeInteger tout) {
|
|
tflow.meta_2.choice.indep_group.timeout_ := timeout_range(tout);
|
|
}
|
|
|
|
/* shared init function for all test cases */
|
|
function init() runs on dummy_CT {
|
|
if (initialized) {
|
|
return;
|
|
}
|
|
map(self:TUN, system:TUN);
|
|
map(self:TUN2, system:TUN);
|
|
initialized := true;
|
|
}
|
|
|
|
/* generate pkt_info based on arguments */
|
|
function pkti_gen(in nfct_direction direction, in octetstring payload := ''O,
|
|
in NonNegativeInteger trunc_len := 0, in boolean exp_pass := true) return pkt_info {
|
|
var pkt_info pkti := {
|
|
direction := direction,
|
|
payload := payload,
|
|
trunc_len := trunc_len,
|
|
exp_pass := exp_pass
|
|
};
|
|
return pkti;
|
|
}
|
|
|
|
/* just verify that we can get conntrack XML and parse it */
|
|
testcase TC_xml() runs on dummy_CT {
|
|
var charstring xml;
|
|
init();
|
|
xml := f_get_conntracks_xml()
|
|
log(xml)
|
|
log(dec_Flows(unichar2oct(xml)))
|
|
}
|
|
|
|
|
|
/* test for classic netfilter UDP '3way handshake' ORIG, REPL, ORIG */
|
|
testcase TC_udp_3way() runs on dummy_CT {
|
|
var flow_info flowi := flow_gen(get_random_port_offset())
|
|
var Flow ct;
|
|
var template Flow t_flow;
|
|
init();
|
|
|
|
log("First packet (ORIG): We expect to create conntrack: unreplied, 30s");
|
|
flow_send_pkt_tun1(flowi, pkti_gen(DIR_ORIG))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
t_flow.meta_2.choice.indep_group.unreplied := {};
|
|
tflow_set_timeout(t_flow, 30);
|
|
get_nfct_and_match(flowi, t_flow);
|
|
|
|
log("Second packet (REPLY): Unreplied should go, still 30s");
|
|
flow_send_pkt_tun2(flowi, pkti_gen(DIR_REPLY))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
tflow_set_timeout(t_flow, 30);
|
|
get_nfct_and_match(flowi, t_flow);
|
|
|
|
log("Third packet (ORIG): Assured, 180s");
|
|
flow_send_pkt_tun1(flowi, pkti_gen(DIR_ORIG))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
t_flow.meta_2.choice.indep_group.assured := {};
|
|
tflow_set_timeout(t_flow, 180);
|
|
get_nfct_and_match(flowi, t_flow);
|
|
|
|
setverdict(pass);
|
|
}
|
|
|
|
/* test for inverse netfilter UDP '3way handshake' ORIG, REPL, REPL */
|
|
testcase TC_udp_uni2() runs on dummy_CT {
|
|
var flow_info flowi := flow_gen(get_random_port_offset());
|
|
var template Flow t_flow;
|
|
var integer i;
|
|
init();
|
|
|
|
for (i := 1; i <= 2; i := i+1) {
|
|
log("Packet (ORIG): We expect to create conntrack: unreplied, 30s");
|
|
flow_send_pkt_tun1(flowi, pkti_gen(DIR_ORIG))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
t_flow.meta_2.choice.indep_group.unreplied := {};
|
|
tflow_set_timeout(t_flow, 30);
|
|
get_nfct_and_match(flowi, t_flow);
|
|
}
|
|
|
|
log("First (REPLY): Unreplied should go, still 30s");
|
|
flow_send_pkt_tun2(flowi, pkti_gen(DIR_REPLY))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
tflow_set_timeout(t_flow, 30);
|
|
get_nfct_and_match(flowi, t_flow);
|
|
|
|
log("Second (REPLY): now 180s");
|
|
flow_send_pkt_tun2(flowi, pkti_gen(DIR_REPLY))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
t_flow.meta_2.choice.indep_group.assured := {};
|
|
tflow_set_timeout(t_flow, 180);
|
|
get_nfct_and_match(flowi, t_flow);
|
|
|
|
setverdict(pass);
|
|
}
|
|
|
|
/* truncated UDP header should neither pass nor create conntrack */
|
|
testcase TC_udp_shorthdr() runs on dummy_CT {
|
|
var flow_info flowi := flow_gen(get_random_port_offset());
|
|
var template Flow t_flow;
|
|
init();
|
|
|
|
log("First packet (ORIG): We expect to create no conntrack");
|
|
flow_send_pkt_tun1(flowi, pkti_gen(DIR_ORIG, -, 20+5, false))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
get_nfct_ensure_none(flowi);
|
|
|
|
setverdict(pass);
|
|
}
|
|
|
|
/* truncated UDP packet (full hdr, short data) should neither pass nor create conntrack */
|
|
testcase TC_udp_shortdata() runs on dummy_CT {
|
|
var flow_info flowi := flow_gen(get_random_port_offset());
|
|
var template Flow t_flow;
|
|
init();
|
|
|
|
log("First packet (ORIG): We expect to create no conntrack");
|
|
flow_send_pkt_tun1(flowi, pkti_gen(DIR_ORIG, -, 20+8+5, false))
|
|
t_flow := f_nfct_templ_from_flow(flowi);
|
|
get_nfct_ensure_none(flowi);
|
|
|
|
setverdict(pass);
|
|
}
|
|
|
|
control {
|
|
//execute(TC_xml());
|
|
execute(TC_udp_3way());
|
|
execute(TC_udp_uni2());
|
|
execute(TC_udp_shorthdr());
|
|
execute(TC_udp_shortdata());
|
|
}
|
|
}
|