1/*-
2 * alias_smedia.c
3 *
4 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause
5 *
6 * Copyright (c) 2000 Whistle Communications, Inc.
7 * All rights reserved.
8 *
9 * Subject to the following obligations and disclaimer of warranty, use and
10 * redistribution of this software, in source or object code forms, with or
11 * without modifications are expressly permitted by Whistle Communications;
12 * provided, however, that:
13 * 1. Any and all reproductions of the source or object code must include the
14 *    copyright notice above and the following disclaimer of warranties; and
15 * 2. No rights are granted, in any manner or form, to use Whistle
16 *    Communications, Inc. trademarks, including the mark "WHISTLE
17 *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
18 *    such appears in the above copyright notice or in the software.
19 *
20 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
21 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
22 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
23 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
25 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
26 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
27 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
28 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
29 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
30 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
31 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
36 * OF SUCH DAMAGE.
37 *
38 * Copyright (c) 2000  Junichi SATOH <junichi@astec.co.jp>
39 *                                   <junichi@junichi.org>
40 * All rights reserved.
41 *
42 * Redistribution and use in source and binary forms, with or without
43 * modification, are permitted provided that the following conditions
44 * are met:
45 * 1. Redistributions of source code must retain the above copyright
46 *    notice, this list of conditions and the following disclaimer.
47 * 2. Redistributions in binary form must reproduce the above copyright
48 *    notice, this list of conditions and the following disclaimer in the
49 *    documentation and/or other materials provided with the distribution.
50 *
51 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
52 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
53 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
54 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
55 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
56 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
57 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
58 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
59 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
60 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61 * SUCH DAMAGE.
62 *
63 * Authors: Erik Salander <erik@whistle.com>
64 *          Junichi SATOH <junichi@astec.co.jp>
65 *                        <junichi@junichi.org>
66 */
67
68#include <sys/cdefs.h>
69__FBSDID("$FreeBSD$");
70
71/*
72   Alias_smedia.c is meant to contain the aliasing code for streaming media
73   protocols.  It performs special processing for RSTP sessions under TCP.
74   Specifically, when a SETUP request is sent by a client, or a 200 reply
75   is sent by a server, it is intercepted and modified.  The address is
76   changed to the gateway machine and an aliasing port is used.
77
78   More specifically, the "client_port" configuration parameter is
79   parsed for SETUP requests.  The "server_port" configuration parameter is
80   parsed for 200 replies eminating from a server.  This is intended to handle
81   the unicast case.
82
83   RTSP also allows a redirection of a stream to another client by using the
84   "destination" configuration parameter.  The destination config parm would
85   indicate a different IP address.  This function is NOT supported by the
86   RTSP translation code below.
87
88   The RTSP multicast functions without any address translation intervention.
89
90   For this routine to work, the SETUP/200 must fit entirely
91   into a single TCP packet.  This is typically the case, but exceptions
92   can easily be envisioned under the actual specifications.
93
94   Probably the most troubling aspect of the approach taken here is
95   that the new SETUP/200 will typically be a different length, and
96   this causes a certain amount of bookkeeping to keep track of the
97   changes of sequence and acknowledgment numbers, since the client
98   machine is totally unaware of the modification to the TCP stream.
99
100   Initial version:  May, 2000 (eds)
101*/
102
103#ifdef _KERNEL
104#include <sys/param.h>
105#include <sys/systm.h>
106#include <sys/kernel.h>
107#include <sys/module.h>
108#else
109#include <errno.h>
110#include <sys/types.h>
111#include <stdio.h>
112#include <string.h>
113#endif
114
115#include <netinet/in_systm.h>
116#include <netinet/in.h>
117#include <netinet/ip.h>
118#include <netinet/tcp.h>
119
120#ifdef _KERNEL
121#include <netinet/libalias/alias.h>
122#include <netinet/libalias/alias_local.h>
123#include <netinet/libalias/alias_mod.h>
124#else
125#include "alias_local.h"
126#include "alias_mod.h"
127#endif
128
129#define RTSP_CONTROL_PORT_NUMBER_1 554
130#define RTSP_CONTROL_PORT_NUMBER_2 7070
131#define TFTP_PORT_NUMBER 69
132
133static void
134AliasHandleRtspOut(struct libalias *, struct ip *, struct alias_link *,
135    int maxpacketsize);
136static int
137fingerprint(struct libalias *la, struct alias_data *ah)
138{
139	if (ah->dport != NULL && ah->aport != NULL && ah->sport != NULL &&
140	    ntohs(*ah->dport) == TFTP_PORT_NUMBER)
141		return (0);
142	if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL ||
143	    ah->maxpktsize == 0)
144		return (-1);
145	if (ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_1
146	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_1
147	    || ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_2
148	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_2)
149		return (0);
150	return (-1);
151}
152
153static int
154protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
155{
156	if (ntohs(*ah->dport) == TFTP_PORT_NUMBER)
157		FindRtspOut(la, pip->ip_src, pip->ip_dst,
158		    *ah->sport, *ah->aport, IPPROTO_UDP);
159	else AliasHandleRtspOut(la, pip, ah->lnk, ah->maxpktsize);
160	return (0);
161}
162
163struct proto_handler handlers[] = {
164	{
165	  .pri = 100,
166	  .dir = OUT,
167	  .proto = TCP|UDP,
168	  .fingerprint = &fingerprint,
169	  .protohandler = &protohandler
170	},
171	{ EOH }
172};
173
174static int
175mod_handler(module_t mod, int type, void *data)
176{
177	int error;
178
179	switch (type) {
180	case MOD_LOAD:
181		error = 0;
182		LibAliasAttachHandlers(handlers);
183		break;
184	case MOD_UNLOAD:
185		error = 0;
186		LibAliasDetachHandlers(handlers);
187		break;
188	default:
189		error = EINVAL;
190	}
191	return (error);
192}
193
194#ifdef _KERNEL
195static
196#endif
197moduledata_t alias_mod = {
198       "alias_smedia", mod_handler, NULL
199};
200
201#ifdef _KERNEL
202DECLARE_MODULE(alias_smedia, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
203MODULE_VERSION(alias_smedia, 1);
204MODULE_DEPEND(alias_smedia, libalias, 1, 1, 1);
205#endif
206
207#define RTSP_CONTROL_PORT_NUMBER_1    554
208#define RTSP_CONTROL_PORT_NUMBER_2   7070
209#define RTSP_PORT_GROUP			2
210
211#define ISDIGIT(a) (((a) >= '0') && ((a) <= '9'))
212
213static int
214search_string(char *data, int dlen, const char *search_str)
215{
216	int i, j, k;
217	int search_str_len;
218
219	search_str_len = strlen(search_str);
220	for (i = 0; i < dlen - search_str_len; i++) {
221		for (j = i, k = 0; j < dlen - search_str_len; j++, k++) {
222			if (data[j] != search_str[k] &&
223			    data[j] != search_str[k] - ('a' - 'A'))
224				break;
225			if (k == search_str_len - 1)
226				return (j + 1);
227		}
228	}
229	return (-1);
230}
231
232static int
233alias_rtsp_out(struct libalias *la, struct ip *pip,
234    struct alias_link *lnk,
235    char *data,
236    const char *port_str)
237{
238	int hlen, tlen, dlen;
239	struct tcphdr *tc;
240	int i, j, pos, state, port_dlen, new_dlen, delta;
241	u_short p[2], new_len;
242	u_short sport, eport, base_port;
243	u_short salias = 0, ealias = 0, base_alias = 0;
244	const char *transport_str = "transport:";
245	char newdata[2048], *port_data, *port_newdata, stemp[80];
246	int links_created = 0, pkt_updated = 0;
247	struct alias_link *rtsp_lnk = NULL;
248	struct in_addr null_addr;
249
250	/* Calculate data length of TCP packet */
251	tc = (struct tcphdr *)ip_next(pip);
252	hlen = (pip->ip_hl + tc->th_off) << 2;
253	tlen = ntohs(pip->ip_len);
254	dlen = tlen - hlen;
255
256	/* Find keyword, "Transport: " */
257	pos = search_string(data, dlen, transport_str);
258	if (pos < 0)
259		return (-1);
260
261	port_data = data + pos;
262	port_dlen = dlen - pos;
263
264	memcpy(newdata, data, pos);
265	port_newdata = newdata + pos;
266
267	while (port_dlen > (int)strlen(port_str)) {
268		/* Find keyword, appropriate port string */
269		pos = search_string(port_data, port_dlen, port_str);
270		if (pos < 0)
271			break;
272
273		memcpy(port_newdata, port_data, pos + 1);
274		port_newdata += (pos + 1);
275
276		p[0] = p[1] = 0;
277		sport = eport = 0;
278		state = 0;
279		for (i = pos; i < port_dlen; i++) {
280			switch (state) {
281			case 0:
282				if (port_data[i] == '=')
283					state++;
284				break;
285			case 1:
286				if (ISDIGIT(port_data[i]))
287					p[0] = p[0] * 10 + port_data[i] - '0';
288				else if (port_data[i] == ';')
289					state = 3;
290				else if (port_data[i] == '-')
291					state++;
292				break;
293			case 2:
294				if (ISDIGIT(port_data[i]))
295					p[1] = p[1] * 10 + port_data[i] - '0';
296				else
297					state++;
298				break;
299			case 3:
300				base_port = p[0];
301				sport = htons(p[0]);
302				eport = htons(p[1]);
303
304				if (!links_created) {
305					links_created = 1;
306					/*
307					 * Find an even numbered port
308					 * number base that satisfies the
309					 * contiguous number of ports we
310					 * need
311					 */
312					null_addr.s_addr = 0;
313					if (0 == (salias = FindNewPortGroup(la, null_addr,
314					    FindAliasAddress(la, pip->ip_src),
315					    sport, 0,
316					    RTSP_PORT_GROUP,
317					    IPPROTO_UDP, 1))) {
318#ifdef LIBALIAS_DEBUG
319						fprintf(stderr,
320						    "PacketAlias/RTSP: Cannot find contiguous RTSP data ports\n");
321#endif
322					} else {
323						base_alias = ntohs(salias);
324						for (j = 0; j < RTSP_PORT_GROUP; j++) {
325							/*
326							 * Establish link
327							 * to port found in
328							 * RTSP packet
329							 */
330							rtsp_lnk = FindRtspOut(la, GetOriginalAddress(lnk), null_addr,
331							    htons(base_port + j), htons(base_alias + j),
332							    IPPROTO_UDP);
333							if (rtsp_lnk != NULL) {
334#ifndef NO_FW_PUNCH
335								/*
336								 * Punch
337								 * hole in
338								 * firewall
339								 */
340								PunchFWHole(rtsp_lnk);
341#endif
342							} else {
343#ifdef LIBALIAS_DEBUG
344								fprintf(stderr,
345								    "PacketAlias/RTSP: Cannot allocate RTSP data ports\n");
346#endif
347								break;
348							}
349						}
350					}
351					ealias = htons(base_alias + (RTSP_PORT_GROUP - 1));
352				}
353				if (salias && rtsp_lnk) {
354					pkt_updated = 1;
355
356					/* Copy into IP packet */
357					sprintf(stemp, "%d", ntohs(salias));
358					memcpy(port_newdata, stemp, strlen(stemp));
359					port_newdata += strlen(stemp);
360
361					if (eport != 0) {
362						*port_newdata = '-';
363						port_newdata++;
364
365						/* Copy into IP packet */
366						sprintf(stemp, "%d", ntohs(ealias));
367						memcpy(port_newdata, stemp, strlen(stemp));
368						port_newdata += strlen(stemp);
369					}
370					*port_newdata = ';';
371					port_newdata++;
372				}
373				state++;
374				break;
375			}
376			if (state > 3) {
377				break;
378			}
379		}
380		port_data += i;
381		port_dlen -= i;
382	}
383
384	if (!pkt_updated)
385		return (-1);
386
387	memcpy(port_newdata, port_data, port_dlen);
388	port_newdata += port_dlen;
389	*port_newdata = '\0';
390
391	/* Create new packet */
392	new_dlen = port_newdata - newdata;
393	memcpy(data, newdata, new_dlen);
394
395	SetAckModified(lnk);
396	tc = (struct tcphdr *)ip_next(pip);
397	delta = GetDeltaSeqOut(tc->th_seq, lnk);
398	AddSeq(lnk, delta + new_dlen - dlen, pip->ip_hl, pip->ip_len,
399	    tc->th_seq, tc->th_off);
400
401	new_len = htons(hlen + new_dlen);
402	DifferentialChecksum(&pip->ip_sum, &new_len, &pip->ip_len, 1);
403	pip->ip_len = new_len;
404
405	tc->th_sum = 0;
406#ifdef _KERNEL
407	tc->th_x2 = 1;
408#else
409	tc->th_sum = TcpChecksum(pip);
410#endif
411	return (0);
412}
413
414/* Support the protocol used by early versions of RealPlayer */
415
416static int
417alias_pna_out(struct libalias *la, struct ip *pip,
418    struct alias_link *lnk,
419    char *data,
420    int dlen)
421{
422	struct alias_link *pna_links;
423	u_short msg_id, msg_len;
424	char *work;
425	u_short alias_port, port;
426	struct tcphdr *tc;
427
428	work = data;
429	work += 5;
430	while (work + 4 < data + dlen) {
431		memcpy(&msg_id, work, 2);
432		work += 2;
433		memcpy(&msg_len, work, 2);
434		work += 2;
435		if (ntohs(msg_id) == 0) /* end of options */
436			return (0);
437
438		if ((ntohs(msg_id) == 1) || (ntohs(msg_id) == 7)) {
439			memcpy(&port, work, 2);
440			pna_links = FindUdpTcpOut(la, pip->ip_src, GetDestAddress(lnk),
441			    port, 0, IPPROTO_UDP, 1);
442			if (pna_links != NULL) {
443#ifndef NO_FW_PUNCH
444				/* Punch hole in firewall */
445				PunchFWHole(pna_links);
446#endif
447				tc = (struct tcphdr *)ip_next(pip);
448				alias_port = GetAliasPort(pna_links);
449				memcpy(work, &alias_port, 2);
450
451				/* Compute TCP checksum for revised packet */
452				tc->th_sum = 0;
453#ifdef _KERNEL
454				tc->th_x2 = 1;
455#else
456				tc->th_sum = TcpChecksum(pip);
457#endif
458			}
459		}
460		work += ntohs(msg_len);
461	}
462
463	return (0);
464}
465
466static void
467AliasHandleRtspOut(struct libalias *la, struct ip *pip, struct alias_link *lnk, int maxpacketsize)
468{
469	int hlen, tlen, dlen;
470	struct tcphdr *tc;
471	char *data;
472	const char *setup = "SETUP", *pna = "PNA", *str200 = "200";
473	const char *okstr = "OK", *client_port_str = "client_port";
474	const char *server_port_str = "server_port";
475	int i, parseOk;
476
477	(void)maxpacketsize;
478
479	tc = (struct tcphdr *)ip_next(pip);
480	hlen = (pip->ip_hl + tc->th_off) << 2;
481	tlen = ntohs(pip->ip_len);
482	dlen = tlen - hlen;
483
484	data = (char *)pip;
485	data += hlen;
486
487	/* When aliasing a client, check for the SETUP request */
488	if ((ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_1) ||
489	    (ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_2)) {
490		if (dlen >= (int)strlen(setup) &&
491		    memcmp(data, setup, strlen(setup)) == 0) {
492			alias_rtsp_out(la, pip, lnk, data, client_port_str);
493			return;
494		}
495
496		if (dlen >= (int)strlen(pna) &&
497		    memcmp(data, pna, strlen(pna)) == 0)
498			alias_pna_out(la, pip, lnk, data, dlen);
499	} else {
500		/*
501		 * When aliasing a server, check for the 200 reply
502		 * Accommodate varying number of blanks between 200 & OK
503		 */
504
505		if (dlen >= (int)strlen(str200)) {
506			for (parseOk = 0, i = 0;
507			    i <= dlen - (int)strlen(str200);
508			    i++)
509				if (memcmp(&data[i], str200, strlen(str200)) == 0) {
510					parseOk = 1;
511					break;
512				}
513
514			if (parseOk) {
515				i += strlen(str200);	/* skip string found */
516				while (data[i] == ' ')	/* skip blank(s) */
517					i++;
518
519				if ((dlen - i) >= (int)strlen(okstr))
520					if (memcmp(&data[i], okstr, strlen(okstr)) == 0)
521						alias_rtsp_out(la, pip, lnk, data, server_port_str);
522			}
523		}
524	}
525}
526