aboutsummaryrefslogtreecommitdiffstats
path: root/ggsn/pco.c
blob: c71f07deb68db03261f17892ab95ab34e1621b21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*
 * PCO parsing related code
 * Copyright (C) 2002, 2003, 2004 Mondru AB.
 * Copyright (C) 2017-2019 by Harald Welte <laforge@gnumonks.org>
 * Copyright (C) 2019 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 *
 * The contents of this file may be used under the terms of the GNU
 * General Public License Version 2, provided that the above copyright
 * notice and this permission notice is included in all copies or
 * substantial portions of the software.
 *
 */

#include <unistd.h>
#include <string.h>

#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>

#include "../lib/util.h"

#include "pco.h"
#include "ggsn.h"

/* determine if IPCP contains given option */
static const uint8_t *ipcp_contains_option(const struct ipcp_hdr *ipcp, size_t ipcp_len,
					   enum ipcp_options opt, size_t opt_minlen)
{
	const uint8_t *cur_opt = ipcp->options;

	/* iterate over Options and check if protocol contained */
	while (cur_opt + sizeof(struct ipcp_option_hdr) <= (uint8_t*)ipcp + ipcp_len) {
		const struct ipcp_option_hdr *cur_opt_hdr = (const struct ipcp_option_hdr *)cur_opt;
		/* length value includes 2 bytes type/length */
		if (cur_opt_hdr->len < sizeof(struct ipcp_option_hdr))
			return NULL;
		if (cur_opt_hdr->type == opt &&
		    cur_opt_hdr->len >= sizeof(struct ipcp_option_hdr) + opt_minlen)
			return cur_opt;
		cur_opt += cur_opt_hdr->len;
	}
	return NULL;
}


static const char *pap_welcome = "Welcome to OsmoGGSN " PACKAGE_VERSION;

/* Handle PAP protocol according to RFC 1334 */
static void process_pco_element_pap(const struct pco_element *pco_in, struct msgb *resp,
				    const struct apn_ctx *apn, struct pdp_t *pdp)
{
	const struct pap_element *pap_in = (const struct pap_element *) pco_in->data;
	uint16_t pap_in_len;
	uint8_t peer_id_len;
	const uint8_t *peer_id;
	unsigned int pap_welcome_len;
	uint8_t pap_out_size;
	struct pap_element *pap_out;

	if (pco_in->length < sizeof(struct pap_element))
		goto ret_broken;

	pap_in_len = osmo_load16be(&pap_in->len);
	if (pco_in->length < pap_in_len)
		goto ret_broken;
	/* "pco_in->length > pap_in_len" is allowed: RFC1334 2.2 states:
	   "Octets outside the range of the Length field should be treated as
	   Data Link Layer padding and should be ignored on reception."
	 */

	switch (pap_in->code) {
	case PAP_CODE_AUTH_REQ:
		if (pap_in_len < sizeof(struct pap_element) + 1)
			goto ret_broken_auth;
		peer_id_len = pap_in->data[0];
		if (pap_in_len < sizeof(struct pap_element) + 1 + peer_id_len)
			goto ret_broken_auth;
		peer_id = &pap_in->data[1];
		LOGPPDP(LOGL_DEBUG, pdp, "PCO PAP PeerId = %s, ACKing\n",
			osmo_quote_str((const char *)peer_id, peer_id_len));
		/* Password-Length + Password following here, but we don't care */

		/* Prepare response, we ACK all of them: */
		pap_welcome_len = strlen(pap_welcome);
		/* +1: Length field of pap_welcome Message */
		pap_out_size = sizeof(struct pap_element) + 1 + pap_welcome_len;
		pap_out = alloca(pap_out_size);
		pap_out->code = PAP_CODE_AUTH_ACK;
		pap_out->id = pap_in->id;
		pap_out->len = htons(pap_out_size);
		pap_out->data[0] = pap_welcome_len;
		memcpy(pap_out->data+1, pap_welcome, pap_welcome_len);
		msgb_t16lv_put(resp, PCO_P_PAP, pap_out_size, (uint8_t *) pap_out);
		break;
	case PAP_CODE_AUTH_ACK:
	case PAP_CODE_AUTH_NAK:
	default:
		LOGPPDP(LOGL_NOTICE, pdp, "Unsupported PAP PCO Code %u, ignoring\n", pap_in->code);
		break;
	}
	return;

ret_broken_auth:
	LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP AuthenticateReq: %s, ignoring\n",
		osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
	return;

ret_broken:
	LOGPPDP(LOGL_NOTICE, pdp, "Invalid PAP PCO Length: %s, ignoring\n",
		osmo_hexdump_nospc((const uint8_t *)pco_in, pco_in->length));
}

/* Handle IP Control Protocol, RFC 1332, extensions in RFC 1877 */
static void process_pco_element_ipcp(const struct pco_element *pco_elem, struct msgb *resp,
				     const struct apn_ctx *apn, struct pdp_t *pdp)
{
	struct ippoolm_t *peer_v4 = pdp_get_peer_ipv(pdp, false);
	const struct in46_addr *dns1 = &apn->v4.cfg.dns[0];
	const struct in46_addr *dns2 = &apn->v4.cfg.dns[1];
	uint8_t *start = resp->tail;
	const struct ipcp_hdr *ipcp;
	uint16_t ipcp_len;
	uint8_t *len1, *len2;
	unsigned int len_appended;
	ptrdiff_t consumed;
	size_t remain;

	if (!peer_v4) {
		LOGPPDP(LOGL_ERROR, pdp, "IPCP but no IPv4 type ?!?\n");
		return;
	}

	ipcp = (const struct ipcp_hdr *)pco_elem->data;
	consumed = (pco_elem->data - &pdp->pco_req.v[0]);
	remain = sizeof(pdp->pco_req.v) - consumed;
	ipcp_len = osmo_load16be(&ipcp->len);
	if (remain < 0 || remain < ipcp_len) {
		LOGPPDP(LOGL_ERROR, pdp, "Malformed IPCP, ignoring\n");
		return;
	}

	/* Three byte T16L header */
	msgb_put_u16(resp, 0x8021);	/* IPCP */
	len1 = msgb_put(resp, 1);	/* Length of contents: delay */

	msgb_put_u8(resp, 0x02);	/* ACK */
	msgb_put_u8(resp, ipcp->id);	/* ID: Needs to match request */
	msgb_put_u8(resp, 0x00);	/* Length MSB */
	len2 = msgb_put(resp, 1);	/* Length LSB: delay */

	if (dns1->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_PRIMARY_DNS, 4)) {
		msgb_put_u8(resp, 0x81);		/* DNS1 Tag */
		msgb_put_u8(resp, 2 + dns1->len);	/* DNS1 Length, incl. TL */
		msgb_put_u32(resp, ntohl(dns1->v4.s_addr));
	}

	if (dns2->len == 4 && ipcp_contains_option(ipcp, ipcp_len, IPCP_OPT_SECONDARY_DNS, 4)) {
		msgb_put_u8(resp, 0x83);		/* DNS2 Tag */
		msgb_put_u8(resp, 2 + dns2->len);	/* DNS2 Length, incl. TL */
		msgb_put_u32(resp, ntohl(dns2->v4.s_addr));
	}

	/* patch in length values */
	len_appended = resp->tail - start;
	*len1 = len_appended - 3;
	*len2 = len_appended - 3;
}

static void process_pco_element_dns_ipv6(const struct pco_element *pco_elem, struct msgb *resp,
					 const struct apn_ctx *apn, struct pdp_t *pdp)
{
	unsigned int i;
	const uint8_t *tail = resp->tail;

	for (i = 0; i < ARRAY_SIZE(apn->v6.cfg.dns); i++) {
		const struct in46_addr *i46a = &apn->v6.cfg.dns[i];
		if (i46a->len != 16)
			continue;
		msgb_t16lv_put(resp, PCO_P_DNS_IPv6_ADDR, i46a->len, i46a->v6.s6_addr);
	}
	if (resp->tail == tail)
		LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv6 DNS, but APN has none configured\n");
}

static void process_pco_element_dns_ipv4(const struct pco_element *pco_elem, struct msgb *resp,
					 const struct apn_ctx *apn, struct pdp_t *pdp)
{
	unsigned int i;
	const uint8_t *tail = resp->tail;

	for (i = 0; i < ARRAY_SIZE(apn->v4.cfg.dns); i++) {
		const struct in46_addr *i46a = &apn->v4.cfg.dns[i];
		if (i46a->len != 4)
			continue;
		msgb_t16lv_put(resp, PCO_P_DNS_IPv4_ADDR, i46a->len, (uint8_t *)&i46a->v4);
	}
	if (resp->tail == tail)
		LOGPPDP(LOGL_NOTICE, pdp, "MS requested IPv4 DNS, but APN has none configured\n");
}

static void process_pco_element(const struct pco_element *pco_elem, struct msgb *resp,
				const struct apn_ctx *apn, struct pdp_t *pdp)
{
	uint16_t protocol_id = osmo_load16be(&pco_elem->protocol_id);

	LOGPPDP(LOGL_DEBUG, pdp, "PCO Protocol 0x%04x\n", protocol_id);
	switch (protocol_id) {
	case PCO_P_PAP:
		process_pco_element_pap(pco_elem, resp, apn, pdp);
		break;
	case PCO_P_IPCP:
		process_pco_element_ipcp(pco_elem, resp, apn, pdp);
		break;
	case PCO_P_DNS_IPv6_ADDR:
		process_pco_element_dns_ipv6(pco_elem, resp, apn, pdp);
		break;
	case PCO_P_DNS_IPv4_ADDR:
		process_pco_element_dns_ipv4(pco_elem, resp, apn, pdp);
		break;
	default:
		LOGPPDP(LOGL_INFO, pdp, "Unknown/Unimplemented PCO Protocol 0x%04x: %s\n",
			protocol_id, osmo_hexdump_nospc(pco_elem->data, pco_elem->length));
		break;
	}
}

/* process one PCO request from a MS/UE, putting together the proper responses */
void process_pco(const struct apn_ctx *apn, struct pdp_t *pdp)
{
	struct msgb *resp = msgb_alloc(256, "PCO.resp");
	const struct ul255_t *pco = &pdp->pco_req;
	const struct pco_element *pco_elem;
	const uint8_t *cur;

	/* build the header of the PCO response */
	OSMO_ASSERT(resp);
	msgb_put_u8(resp, 0x80); /* ext-bit + configuration protocol byte */

	/* iterate over the PCO elements in the request; call process_pco_element() for each */
	for (cur = pco->v + 1, pco_elem = (const struct pco_element *) cur;
	     cur + sizeof(struct pco_element) <= pco->v + pco->l;
	     cur += pco_elem->length + sizeof(*pco_elem), pco_elem = (const struct pco_element *) cur) {
		process_pco_element(pco_elem, resp, apn, pdp);
	}

	/* copy the PCO response msgb and copy its contents over to the PDP context */
	if (msgb_length(resp) > 1) {
		memcpy(pdp->pco_neg.v, msgb_data(resp), msgb_length(resp));
		pdp->pco_neg.l = msgb_length(resp);
	} else
		pdp->pco_neg.l = 0;
	msgb_free(resp);
}