ggsn: Verify presence of DNS addresses in IPCP of PCO

If we request DNS in IPCP in PCO, we also expect a corresponding result.
diff --git a/ggsn_tests/GGSN_Tests.ttcn b/ggsn_tests/GGSN_Tests.ttcn
index dd9acbc..8b0d5e4 100644
--- a/ggsn_tests/GGSN_Tests.ttcn
+++ b/ggsn_tests/GGSN_Tests.ttcn
@@ -8,6 +8,7 @@
 	import from GTP_CodecPort_CtrlFunct all;
 	import from GTPC_Types all;
 	import from GTPU_Types all;
+	import from IPCP_Types all;
 	import from IP_Types all;
 	import from ICMPv6_Types all;
 	import from Native_Functions all;
@@ -400,6 +401,15 @@
 		}
 	}
 
+	template ProtocolElement tr_PCO_Proto(OCT2 prot_id) := {
+		protocolID := prot_id,
+		lengthProtoID := ?,
+		protoIDContents := ?
+	}
+	template ProtConfigOptions tr_PCO_Contains(OCT2 prot_id) modifies tr_PCO := {
+		protocols := { *, tr_PCO_Proto(prot_id), * }
+	}
+
 	template ProtConfigOptions ts_PCO_IPv4_DNS_CONT modifies ts_PCO := {
 		protocols := {
 			{ protocolID := '000d'O, lengthProtoID := 0, protoIDContents := ''O }
@@ -411,6 +421,41 @@
 		}
 	}
 
+	/* extract a given protocol payload from PCO */
+	function f_PCO_extract_proto(ProtConfigOptions pco, OCT2 protocol) return octetstring {
+		var integer i;
+		for (i := 0; i < lengthof(pco.protocols); i := i + 1) {
+			if (pco.protocols[i].protocolID == protocol) {
+				return pco.protocols[i].protoIDContents;
+			}
+		}
+		setverdict(fail);
+		return ''O;
+	}
+
+	template IpcpPacket tr_IPCP(template LcpCode code, template uint8_t identifier,
+				    template IpcpOptionList opts) := {
+		code := code,
+		identifier := identifier,
+		len := ?,
+		options := opts
+	}
+	template IpcpOption tr_IPCP_PrimaryDns(template OCT4 addr) := {
+		code := IPCP_OPT_PrimaryDNS,
+		len := 6,
+		data := addr
+	}
+	template IpcpOption tr_IPCP_SecondaryDns(template OCT4 addr) := {
+		code := IPCP_OPT_SecondaryDNS,
+		len := 6,
+		data := addr
+	}
+
+	template IpcpPacket tr_IPCP_Ack_DNS(template uint8_t identifier := ?, template OCT4 dns1 := ?,
+					    template OCT4 dns2 := ?) :=
+		tr_IPCP(LCP_Configure_Ack, identifier,
+				{ *, tr_IPCP_PrimaryDns(dns1), *, tr_IPCP_SecondaryDns(dns2), * });
+
 
 	function f_teardown_ind_IE(in template BIT1 ind) return template TearDownInd {
 /*
@@ -937,6 +982,15 @@
 		var PdpContext ctx := valueof(t_DefinePDP(f_rnd_imsi('26242'H), '1234'O, c_ApnInternet, valueof(t_EuaIPv4Dyn)));
 		ctx.pco_req := valueof(ts_PCO_IPv4_DNS_IPCP);
 		f_pdp_ctx_act(ctx);
+		/* verify IPCP is at all contained */
+		if (not match(ctx.pco_neg, tr_PCO_Contains('8021'O))) {
+			setverdict(fail, "IPCP not found in PCO");
+		}
+		/* verify IPCP contains both primary and secondary DNS */
+		var IpcpPacket ipcp := dec_IpcpPacket(f_PCO_extract_proto(ctx.pco_neg, '8021'O));
+		if (not match(ipcp, tr_IPCP_Ack_DNS)) {
+			setverdict(fail, "Primary/Secondary DNS not found in IPCP");
+		}
 		f_pdp_ctx_del(ctx, '1'B);
 	}
 
diff --git a/ggsn_tests/gen_links.sh b/ggsn_tests/gen_links.sh
index ee8d7b0..6dd2d62 100755
--- a/ggsn_tests/gen_links.sh
+++ b/ggsn_tests/gen_links.sh
@@ -44,5 +44,5 @@
 gen_links $DIR $FILES
 
 DIR=../library
-FILES="General_Types.ttcn GSM_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc"
+FILES="General_Types.ttcn GSM_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPCP_Types.ttcn"
 gen_links $DIR $FILES
diff --git a/library/IPCP_Types.ttcn b/library/IPCP_Types.ttcn
new file mode 100644
index 0000000..75bb092
--- /dev/null
+++ b/library/IPCP_Types.ttcn
@@ -0,0 +1,55 @@
+module IPCP_Types {
+
+import from Osmocom_Types all;
+
+/* RFC1331 Section 6 */
+type enumerated LcpCode {
+	LCP_Configure_Request		('01'O),
+	LCP_Configure_Ack		('02'O),
+	LCP_Configure_Nak		('03'O),
+	LCP_Configure_Reject		('04'O),
+	LCP_Terminate_Requeest		('05'O),
+	LCP_Terminate_Ack		('06'O),
+	LCP_Code_Reject			('07'O),
+	LCP_Protocol_Reject		('08'O),
+	LCP_Echo_Request		('09'O),
+	LCP_Echo_Reply			('10'O),
+	LCP_Discarded_Request		('11'O),
+	LCP_Reserved			('12'O)
+} with { variant "FIELDLENGTH(8)" };
+
+type record IpcpPacket {
+	LcpCode		code,
+	uint8_t		identifier,
+	uint16_t	len,
+	IpcpOptionList	options
+} with { variant (len) "LENGTHTO(code,identifier,len,options)" };
+
+/* RFC1332 */
+type enumerated IpcpConfigOptCode {
+	IPCP_OPT_IpAddresses		('01'O),
+	IPCP_OPT_IpCompressionProtocol	('02'O),
+	IPCP_OPT_IpAddress		('03'O),
+	IPCP_OPT_MobileIPv4		('04'O),/* RFC 2290 */
+	IPCP_OPT_PrimaryDNS		(129),	/* RFC 1877 */
+	IPCP_OPT_PrimaryNBNS		(130),	/* RFC 1877 */
+	IPCP_OPT_SecondaryDNS		(131),	/* RFC 1877 */
+	IPCP_OPT_SecondaryNBNS		(132)	/* RFC 1877 */
+} with { variant "FIELDLENGTH(8)" };
+
+type record IpcpOption {
+	IpcpConfigOptCode	code,
+	uint8_t			len,
+	octetstring		data
+} with { variant (len) "LENGTHTO(code,len,data)" };
+
+type record of IpcpOption IpcpOptionList;
+
+external function enc_IpcpPacket(in IpcpPacket inp) return octetstring
+with { extension "prototype(convert)" extension "encode(RAW)" };
+
+external function dec_IpcpPacket(in octetstring inp) return IpcpPacket
+with { extension "prototype(convert)" extension "decode(RAW)" };
+
+
+} with { encode "RAW" ; variant "FIELDORDER(msb)" }