xref: /freebsd/sys/netinet/libalias/alias_smedia.c (revision 4e8d558c)
1 /*-
2  * alias_smedia.c
3  *
4  * SPDX-License-Identifier: 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 
133 static void
134 AliasHandleRtspOut(struct libalias *, struct ip *, struct alias_link *,
135     int maxpacketsize);
136 static int
137 fingerprint(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 
153 static int
154 protohandler(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 
163 struct 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 
174 static int
175 mod_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
195 static
196 #endif
197 moduledata_t alias_mod = {
198        "alias_smedia", mod_handler, NULL
199 };
200 
201 #ifdef _KERNEL
202 DECLARE_MODULE(alias_smedia, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
203 MODULE_VERSION(alias_smedia, 1);
204 MODULE_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 
213 static int
214 search_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 
232 static int
233 alias_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 = (TH_RES1 >> 8);
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 
416 static int
417 alias_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 = (TH_RES1 >> 8);
455 #else
456 				tc->th_sum = TcpChecksum(pip);
457 #endif
458 			}
459 		}
460 		work += ntohs(msg_len);
461 	}
462 
463 	return (0);
464 }
465 
466 static void
467 AliasHandleRtspOut(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