xref: /freebsd/sys/netinet/libalias/alias_smedia.c (revision cfd6422a5217410fbd66f7a7a8a64d9d85e61229)
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 
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 
140 	if (ah->dport != NULL && ah->aport != NULL && ah->sport != NULL &&
141             ntohs(*ah->dport) == TFTP_PORT_NUMBER)
142 		return (0);
143 	if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL ||
144 	    ah->maxpktsize == 0)
145 		return (-1);
146 	if (ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_1
147 	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_1
148 	    || ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_2
149 	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_2)
150 		return (0);
151 	return (-1);
152 }
153 
154 static int
155 protohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
156 {
157 
158 	if (ntohs(*ah->dport) == TFTP_PORT_NUMBER)
159 		FindRtspOut(la, pip->ip_src, pip->ip_dst,
160  			    *ah->sport, *ah->aport, IPPROTO_UDP);
161 	else AliasHandleRtspOut(la, pip, ah->lnk, ah->maxpktsize);
162 	return (0);
163 }
164 
165 struct proto_handler handlers[] = {
166 	{
167 	  .pri = 100,
168 	  .dir = OUT,
169 	  .proto = TCP|UDP,
170 	  .fingerprint = &fingerprint,
171 	  .protohandler = &protohandler
172 	},
173 	{ EOH }
174 };
175 
176 static int
177 mod_handler(module_t mod, int type, void *data)
178 {
179 	int error;
180 
181 	switch (type) {
182 	case MOD_LOAD:
183 		error = 0;
184 		LibAliasAttachHandlers(handlers);
185 		break;
186 	case MOD_UNLOAD:
187 		error = 0;
188 		LibAliasDetachHandlers(handlers);
189 		break;
190 	default:
191 		error = EINVAL;
192 	}
193 	return (error);
194 }
195 
196 #ifdef _KERNEL
197 static
198 #endif
199 moduledata_t alias_mod = {
200        "alias_smedia", mod_handler, NULL
201 };
202 
203 #ifdef	_KERNEL
204 DECLARE_MODULE(alias_smedia, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
205 MODULE_VERSION(alias_smedia, 1);
206 MODULE_DEPEND(alias_smedia, libalias, 1, 1, 1);
207 #endif
208 
209 #define RTSP_CONTROL_PORT_NUMBER_1 554
210 #define RTSP_CONTROL_PORT_NUMBER_2 7070
211 #define RTSP_PORT_GROUP            2
212 
213 #define ISDIGIT(a) (((a) >= '0') && ((a) <= '9'))
214 
215 static int
216 search_string(char *data, int dlen, const char *search_str)
217 {
218 	int i, j, k;
219 	int search_str_len;
220 
221 	search_str_len = strlen(search_str);
222 	for (i = 0; i < dlen - search_str_len; i++) {
223 		for (j = i, k = 0; j < dlen - search_str_len; j++, k++) {
224 			if (data[j] != search_str[k] &&
225 			    data[j] != search_str[k] - ('a' - 'A')) {
226 				break;
227 			}
228 			if (k == search_str_len - 1) {
229 				return (j + 1);
230 			}
231 		}
232 	}
233 	return (-1);
234 }
235 
236 static int
237 alias_rtsp_out(struct libalias *la, struct ip *pip,
238     struct alias_link *lnk,
239     char *data,
240     const char *port_str)
241 {
242 	int hlen, tlen, dlen;
243 	struct tcphdr *tc;
244 	int i, j, pos, state, port_dlen, new_dlen, delta;
245 	u_short p[2], new_len;
246 	u_short sport, eport, base_port;
247 	u_short salias = 0, ealias = 0, base_alias = 0;
248 	const char *transport_str = "transport:";
249 	char newdata[2048], *port_data, *port_newdata, stemp[80];
250 	int links_created = 0, pkt_updated = 0;
251 	struct alias_link *rtsp_lnk = NULL;
252 	struct in_addr null_addr;
253 
254 	/* Calculate data length of TCP packet */
255 	tc = (struct tcphdr *)ip_next(pip);
256 	hlen = (pip->ip_hl + tc->th_off) << 2;
257 	tlen = ntohs(pip->ip_len);
258 	dlen = tlen - hlen;
259 
260 	/* Find keyword, "Transport: " */
261 	pos = search_string(data, dlen, transport_str);
262 	if (pos < 0) {
263 		return (-1);
264 	}
265 	port_data = data + pos;
266 	port_dlen = dlen - pos;
267 
268 	memcpy(newdata, data, pos);
269 	port_newdata = newdata + pos;
270 
271 	while (port_dlen > (int)strlen(port_str)) {
272 		/* Find keyword, appropriate port string */
273 		pos = search_string(port_data, port_dlen, port_str);
274 		if (pos < 0) {
275 			break;
276 		}
277 		memcpy(port_newdata, port_data, pos + 1);
278 		port_newdata += (pos + 1);
279 
280 		p[0] = p[1] = 0;
281 		sport = eport = 0;
282 		state = 0;
283 		for (i = pos; i < port_dlen; i++) {
284 			switch (state) {
285 			case 0:
286 				if (port_data[i] == '=') {
287 					state++;
288 				}
289 				break;
290 			case 1:
291 				if (ISDIGIT(port_data[i])) {
292 					p[0] = p[0] * 10 + port_data[i] - '0';
293 				} else {
294 					if (port_data[i] == ';') {
295 						state = 3;
296 					}
297 					if (port_data[i] == '-') {
298 						state++;
299 					}
300 				}
301 				break;
302 			case 2:
303 				if (ISDIGIT(port_data[i])) {
304 					p[1] = p[1] * 10 + port_data[i] - '0';
305 				} else {
306 					state++;
307 				}
308 				break;
309 			case 3:
310 				base_port = p[0];
311 				sport = htons(p[0]);
312 				eport = htons(p[1]);
313 
314 				if (!links_created) {
315 					links_created = 1;
316 					/*
317 					 * Find an even numbered port
318 					 * number base that satisfies the
319 					 * contiguous number of ports we
320 					 * need
321 					 */
322 					null_addr.s_addr = 0;
323 					if (0 == (salias = FindNewPortGroup(la, null_addr,
324 					    FindAliasAddress(la, pip->ip_src),
325 					    sport, 0,
326 					    RTSP_PORT_GROUP,
327 					    IPPROTO_UDP, 1))) {
328 #ifdef LIBALIAS_DEBUG
329 						fprintf(stderr,
330 						    "PacketAlias/RTSP: Cannot find contiguous RTSP data ports\n");
331 #endif
332 					} else {
333 						base_alias = ntohs(salias);
334 						for (j = 0; j < RTSP_PORT_GROUP; j++) {
335 							/*
336 							 * Establish link
337 							 * to port found in
338 							 * RTSP packet
339 							 */
340 							rtsp_lnk = FindRtspOut(la, GetOriginalAddress(lnk), null_addr,
341 							    htons(base_port + j), htons(base_alias + j),
342 							    IPPROTO_UDP);
343 							if (rtsp_lnk != NULL) {
344 #ifndef NO_FW_PUNCH
345 								/*
346 								 * Punch
347 								 * hole in
348 								 * firewall
349 								 */
350 								PunchFWHole(rtsp_lnk);
351 #endif
352 							} else {
353 #ifdef LIBALIAS_DEBUG
354 								fprintf(stderr,
355 								    "PacketAlias/RTSP: Cannot allocate RTSP data ports\n");
356 #endif
357 								break;
358 							}
359 						}
360 					}
361 					ealias = htons(base_alias + (RTSP_PORT_GROUP - 1));
362 				}
363 				if (salias && rtsp_lnk) {
364 					pkt_updated = 1;
365 
366 					/* Copy into IP packet */
367 					sprintf(stemp, "%d", ntohs(salias));
368 					memcpy(port_newdata, stemp, strlen(stemp));
369 					port_newdata += strlen(stemp);
370 
371 					if (eport != 0) {
372 						*port_newdata = '-';
373 						port_newdata++;
374 
375 						/* Copy into IP packet */
376 						sprintf(stemp, "%d", ntohs(ealias));
377 						memcpy(port_newdata, stemp, strlen(stemp));
378 						port_newdata += strlen(stemp);
379 					}
380 					*port_newdata = ';';
381 					port_newdata++;
382 				}
383 				state++;
384 				break;
385 			}
386 			if (state > 3) {
387 				break;
388 			}
389 		}
390 		port_data += i;
391 		port_dlen -= i;
392 	}
393 
394 	if (!pkt_updated)
395 		return (-1);
396 
397 	memcpy(port_newdata, port_data, port_dlen);
398 	port_newdata += port_dlen;
399 	*port_newdata = '\0';
400 
401 	/* Create new packet */
402 	new_dlen = port_newdata - newdata;
403 	memcpy(data, newdata, new_dlen);
404 
405 	SetAckModified(lnk);
406 	tc = (struct tcphdr *)ip_next(pip);
407 	delta = GetDeltaSeqOut(tc->th_seq, lnk);
408 	AddSeq(lnk, delta + new_dlen - dlen, pip->ip_hl, pip->ip_len,
409 	    tc->th_seq, tc->th_off);
410 
411 	new_len = htons(hlen + new_dlen);
412 	DifferentialChecksum(&pip->ip_sum,
413 	    &new_len,
414 	    &pip->ip_len,
415 	    1);
416 	pip->ip_len = new_len;
417 
418 	tc->th_sum = 0;
419 #ifdef _KERNEL
420 	tc->th_x2 = 1;
421 #else
422 	tc->th_sum = TcpChecksum(pip);
423 #endif
424 	return (0);
425 }
426 
427 /* Support the protocol used by early versions of RealPlayer */
428 
429 static int
430 alias_pna_out(struct libalias *la, struct ip *pip,
431     struct alias_link *lnk,
432     char *data,
433     int dlen)
434 {
435 	struct alias_link *pna_links;
436 	u_short msg_id, msg_len;
437 	char *work;
438 	u_short alias_port, port;
439 	struct tcphdr *tc;
440 
441 	work = data;
442 	work += 5;
443 	while (work + 4 < data + dlen) {
444 		memcpy(&msg_id, work, 2);
445 		work += 2;
446 		memcpy(&msg_len, work, 2);
447 		work += 2;
448 		if (ntohs(msg_id) == 0) {
449 			/* end of options */
450 			return (0);
451 		}
452 		if ((ntohs(msg_id) == 1) || (ntohs(msg_id) == 7)) {
453 			memcpy(&port, work, 2);
454 			pna_links = FindUdpTcpOut(la, pip->ip_src, GetDestAddress(lnk),
455 			    port, 0, IPPROTO_UDP, 1);
456 			if (pna_links != NULL) {
457 #ifndef NO_FW_PUNCH
458 				/* Punch hole in firewall */
459 				PunchFWHole(pna_links);
460 #endif
461 				tc = (struct tcphdr *)ip_next(pip);
462 				alias_port = GetAliasPort(pna_links);
463 				memcpy(work, &alias_port, 2);
464 
465 				/* Compute TCP checksum for revised packet */
466 				tc->th_sum = 0;
467 #ifdef _KERNEL
468 				tc->th_x2 = 1;
469 #else
470 				tc->th_sum = TcpChecksum(pip);
471 #endif
472 			}
473 		}
474 		work += ntohs(msg_len);
475 	}
476 
477 	return (0);
478 }
479 
480 static void
481 AliasHandleRtspOut(struct libalias *la, struct ip *pip, struct alias_link *lnk, int maxpacketsize)
482 {
483 	int hlen, tlen, dlen;
484 	struct tcphdr *tc;
485 	char *data;
486 	const char *setup = "SETUP", *pna = "PNA", *str200 = "200";
487 	const char *okstr = "OK", *client_port_str = "client_port";
488 	const char *server_port_str = "server_port";
489 	int i, parseOk;
490 
491 	(void)maxpacketsize;
492 
493 	tc = (struct tcphdr *)ip_next(pip);
494 	hlen = (pip->ip_hl + tc->th_off) << 2;
495 	tlen = ntohs(pip->ip_len);
496 	dlen = tlen - hlen;
497 
498 	data = (char *)pip;
499 	data += hlen;
500 
501 	/* When aliasing a client, check for the SETUP request */
502 	if ((ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_1) ||
503 	    (ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_2)) {
504 		if (dlen >= (int)strlen(setup)) {
505 			if (memcmp(data, setup, strlen(setup)) == 0) {
506 				alias_rtsp_out(la, pip, lnk, data, client_port_str);
507 				return;
508 			}
509 		}
510 		if (dlen >= (int)strlen(pna)) {
511 			if (memcmp(data, pna, strlen(pna)) == 0) {
512 				alias_pna_out(la, pip, lnk, data, dlen);
513 			}
514 		}
515 	} else {
516 		/*
517 		 * When aliasing a server, check for the 200 reply
518 		 * Accommodate varying number of blanks between 200 & OK
519 		 */
520 
521 		if (dlen >= (int)strlen(str200)) {
522 			for (parseOk = 0, i = 0;
523 			    i <= dlen - (int)strlen(str200);
524 			    i++) {
525 				if (memcmp(&data[i], str200, strlen(str200)) == 0) {
526 					parseOk = 1;
527 					break;
528 				}
529 			}
530 			if (parseOk) {
531 				i += strlen(str200);	/* skip string found */
532 				while (data[i] == ' ')	/* skip blank(s) */
533 					i++;
534 
535 				if ((dlen - i) >= (int)strlen(okstr)) {
536 					if (memcmp(&data[i], okstr, strlen(okstr)) == 0)
537 						alias_rtsp_out(la, pip, lnk, data, server_port_str);
538 				}
539 			}
540 		}
541 	}
542 }
543