module IPL4_example { 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; external function enc_Flow(in Flow pdu) return octetstring with { extension "prototype (convert) encode(XER:XER_EXTENDED)" } external function dec_Flow(in octetstring stream) return Flow with { extension "prototype (convert) decode(XER:XER_EXTENDED)" } external function enc_Flows(in Flows pdu) return octetstring with { extension "prototype (convert) encode(XER:XER_EXTENDED)" } external function dec_Flows(in octetstring stream) return Flows with { extension "prototype (convert) decode(XER:XER_EXTENDED)" } 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 }; const integer AF_INET := 2; const integer AF_INET6 := 23; import from UsefulTtcn3Types all; type enumerated nfct_direction { DIR_ORIG, DIR_REPLY }; type record flow_info { unsignedbyte l3_protocol, charstring src_ip, charstring dst_ip, unsignedbyte l4_protocol, unsignedshort src_port, unsignedshort dst_port } 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 := "1.1.1.200", dst_ip := "2.2.2.200", 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 := ""; flowi.dst_ip := ""; } 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; } /* generate an IPv4 packet according to flowi + pkti */ function flow_gen_ipv4_pkt(flow_info flowi, pkt_info pkti) return octetstring { var octetstring data; var OCT2 csum; 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) } data := f_IPv4_enc(ip); csum := f_IPv4_checksum(data); data[10] := csum[0]; data[11] := csum[1]; return data; } /* 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 octetstring data; var OCT2 csum; 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 } data := f_IPv6_enc(ip); csum := f_IPv4_checksum(data); data[10] := csum[0]; data[11] := csum[1]; return data; } /* 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; } /* generate a packet according to the input flow + pkt information */ /* function flow_gen_pkt_(flow_info flowi, pkt_info pkti) return octetstring { var octetstring ret; var unsignedshort src_port, dst_port; var charstring src_ip, dst_ip; if (pkti.direction == DIR_ORIG) { src_ip := flowi.src_ip src_port := flowi.src_port dst_ip := flowi.dst_ip dst_port := flowi.dst_port } else { src_ip := flowi.dst_ip src_port := flowi.dst_port dst_ip := flowi.src_ip dst_port := flowi.src_port } if (flowi.l4_protocol == c_ip_proto_udp) { ret := f_IPv4IPv6_AnyUdpPacket(src_ip, dst_ip, src_port, dst_port); } if (pkti.trunc_len > 0 and pkti.trunc_len < lengthof(ret)) { ret := substr(ret, 0, pkti.trunc_len); } return ret } */ /* reverse the L3 portion */ private function f_nfct_l3_reverse(template Layer3_type input) return template Layer3_type { return { protoname := input.protoname, protonum := input.protonum, src := input.dst, dst := input.src } } /* reverse the L4 portion */ private function f_nfct_l4_reverse(template Layer4_type input) return template Layer4_type { return { protoname := input.protoname, protonum := input.protonum, sport := input.dport, dport := input.sport } } /* reverse an Orig_repl_group template */ private function f_nfct_orig_repl_reverse(template Orig_repl_group input) return template Orig_repl_group { var template Orig_repl_group output output.layer3 := f_nfct_l3_reverse(input.layer3) output.layer4 := f_nfct_l4_reverse(input.layer4) output.zone := input.zone /* we cannot assume inverse direction counters have any relation to the forward direction */ output.counters := * return output } function f_proto_to_af(integer proto) return integer { if (proto == c_ip_proto_ipv4) { return AF_INET; } else if (proto == c_ip_proto_ipv6) { return AF_INET6; } else { return 0; } } /* construct a template that can be used to match nf-conntrack XML */ function f_nfct_templ_from_flow(flow_info flowi) return template Flow { /* construct original tuple from flow */ var template Orig_repl_group orig := { layer3 := { protoname := *, protonum := int2str(f_proto_to_af(flowi.l3_protocol)), src := flowi.src_ip, dst := flowi.dst_ip }, layer4 := { protoname := *, protonum := int2str(flowi.l4_protocol), sport := flowi.src_port, dport := flowi.dst_port }, zone := *, counters := * } /* create the inverse of the original tuple */ var template Orig_repl_group repl := f_nfct_orig_repl_reverse(orig) return { meta := { direction := "original", choice := { orig_repl_group := orig } }, meta_1 := { direction := "reply", choice := { orig_repl_group := repl } }, meta_2 := ?, when := * } } /* get a single conntrack entry derived from the specified flow_info */ //{ meta := { direction := "original", choice := { orig_repl_group := { layer3 := { protoname := "ipv4", protonum := "2", src := "1.1.1.200", dst := "2.2.2.200" }, layer4 := { protoname := "udp", protonum := "17", sport := 1001, dport := 2001 }, zone := omit, counters := omit } } }, meta_1 := { direction := "reply", choice := { orig_repl_group := { layer3 := { protoname := "ipv4", protonum := "2", src := "2.2.2.200", dst := "1.1.1.200" }, layer4 := { protoname := "udp", protonum := "17", sport := 2001, dport := 1001 }, zone := omit, counters := omit } } }, meta_2 := { direction := "independent", choice := { indep_group := { state := omit, timeout_ := 30, mark := 0, secmark := omit, zone := omit, use := 2, id := 2741869312, assured := omit, unreplied := { }, timestamp := omit, deltatime := omit } } }, when := omit } function f_get_conntracks(flow_info flowi) return Flows { var charstring xml := f_get_conntrack_xml(flowi.src_ip, flowi.dst_ip, flowi.l4_protocol, flowi.src_port, flowi.dst_port) return dec_Flows(unichar2oct(xml)); } function f_get_conntrack(flow_info flowi) return Flow { var Flows flows := f_get_conntracks(flowi); return flows.flow_list[0]; } /* 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; alt { //[] TUN2.receive(tunrcv(pkt)) { } //[] TUN2.receive { log("unexpected receive"); repeat }; [] TUN2.receive {} [] 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; alt { //[] TUN.receive(tunrcv(pkt)) { } //[] TUN.receive { log("unexpected"); repeat } [] TUN.receive {} [] 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()); } }