xref: /freebsd/sys/netinet/libalias/alias_smedia.c (revision c697fb7f)
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 
316 					links_created = 1;
317 					/*
318 					 * Find an even numbered port
319 					 * number base that satisfies the
320 					 * contiguous number of ports we
321 					 * need
322 					 */
323 					null_addr.s_addr = 0;
324 					if (0 == (salias = FindNewPortGroup(la, null_addr,
325 					    FindAliasAddress(la, pip->ip_src),
326 					    sport, 0,
327 					    RTSP_PORT_GROUP,
328 					    IPPROTO_UDP, 1))) {
329 #ifdef LIBALIAS_DEBUG
330 						fprintf(stderr,
331 						    "PacketAlias/RTSP: Cannot find contiguous RTSP data ports\n");
332 #endif
333 					} else {
334 
335 						base_alias = ntohs(salias);
336 						for (j = 0; j < RTSP_PORT_GROUP; j++) {
337 							/*
338 							 * Establish link
339 							 * to port found in
340 							 * RTSP packet
341 							 */
342 							rtsp_lnk = FindRtspOut(la, GetOriginalAddress(lnk), null_addr,
343 							    htons(base_port + j), htons(base_alias + j),
344 							    IPPROTO_UDP);
345 							if (rtsp_lnk != NULL) {
346 #ifndef NO_FW_PUNCH
347 								/*
348 								 * Punch
349 								 * hole in
350 								 * firewall
351 								 */
352 								PunchFWHole(rtsp_lnk);
353 #endif
354 							} else {
355 #ifdef LIBALIAS_DEBUG
356 								fprintf(stderr,
357 								    "PacketAlias/RTSP: Cannot allocate RTSP data ports\n");
358 #endif
359 								break;
360 							}
361 						}
362 					}
363 					ealias = htons(base_alias + (RTSP_PORT_GROUP - 1));
364 				}
365 				if (salias && rtsp_lnk) {
366 
367 					pkt_updated = 1;
368 
369 					/* Copy into IP packet */
370 					sprintf(stemp, "%d", ntohs(salias));
371 					memcpy(port_newdata, stemp, strlen(stemp));
372 					port_newdata += strlen(stemp);
373 
374 					if (eport != 0) {
375 						*port_newdata = '-';
376 						port_newdata++;
377 
378 						/* Copy into IP packet */
379 						sprintf(stemp, "%d", ntohs(ealias));
380 						memcpy(port_newdata, stemp, strlen(stemp));
381 						port_newdata += strlen(stemp);
382 					}
383 					*port_newdata = ';';
384 					port_newdata++;
385 				}
386 				state++;
387 				break;
388 			}
389 			if (state > 3) {
390 				break;
391 			}
392 		}
393 		port_data += i;
394 		port_dlen -= i;
395 	}
396 
397 	if (!pkt_updated)
398 		return (-1);
399 
400 	memcpy(port_newdata, port_data, port_dlen);
401 	port_newdata += port_dlen;
402 	*port_newdata = '\0';
403 
404 	/* Create new packet */
405 	new_dlen = port_newdata - newdata;
406 	memcpy(data, newdata, new_dlen);
407 
408 	SetAckModified(lnk);
409 	tc = (struct tcphdr *)ip_next(pip);
410 	delta = GetDeltaSeqOut(tc->th_seq, lnk);
411 	AddSeq(lnk, delta + new_dlen - dlen, pip->ip_hl, pip->ip_len,
412 	    tc->th_seq, tc->th_off);
413 
414 	new_len = htons(hlen + new_dlen);
415 	DifferentialChecksum(&pip->ip_sum,
416 	    &new_len,
417 	    &pip->ip_len,
418 	    1);
419 	pip->ip_len = new_len;
420 
421 	tc->th_sum = 0;
422 #ifdef _KERNEL
423 	tc->th_x2 = 1;
424 #else
425 	tc->th_sum = TcpChecksum(pip);
426 #endif
427 	return (0);
428 }
429 
430 /* Support the protocol used by early versions of RealPlayer */
431 
432 static int
433 alias_pna_out(struct libalias *la, struct ip *pip,
434     struct alias_link *lnk,
435     char *data,
436     int dlen)
437 {
438 	struct alias_link *pna_links;
439 	u_short msg_id, msg_len;
440 	char *work;
441 	u_short alias_port, port;
442 	struct tcphdr *tc;
443 
444 	work = data;
445 	work += 5;
446 	while (work + 4 < data + dlen) {
447 		memcpy(&msg_id, work, 2);
448 		work += 2;
449 		memcpy(&msg_len, work, 2);
450 		work += 2;
451 		if (ntohs(msg_id) == 0) {
452 			/* end of options */
453 			return (0);
454 		}
455 		if ((ntohs(msg_id) == 1) || (ntohs(msg_id) == 7)) {
456 			memcpy(&port, work, 2);
457 			pna_links = FindUdpTcpOut(la, pip->ip_src, GetDestAddress(lnk),
458 			    port, 0, IPPROTO_UDP, 1);
459 			if (pna_links != NULL) {
460 #ifndef NO_FW_PUNCH
461 				/* Punch hole in firewall */
462 				PunchFWHole(pna_links);
463 #endif
464 				tc = (struct tcphdr *)ip_next(pip);
465 				alias_port = GetAliasPort(pna_links);
466 				memcpy(work, &alias_port, 2);
467 
468 				/* Compute TCP checksum for revised packet */
469 				tc->th_sum = 0;
470 #ifdef _KERNEL
471 				tc->th_x2 = 1;
472 #else
473 				tc->th_sum = TcpChecksum(pip);
474 #endif
475 			}
476 		}
477 		work += ntohs(msg_len);
478 	}
479 
480 	return (0);
481 }
482 
483 static void
484 AliasHandleRtspOut(struct libalias *la, struct ip *pip, struct alias_link *lnk, int maxpacketsize)
485 {
486 	int hlen, tlen, dlen;
487 	struct tcphdr *tc;
488 	char *data;
489 	const char *setup = "SETUP", *pna = "PNA", *str200 = "200";
490 	const char *okstr = "OK", *client_port_str = "client_port";
491 	const char *server_port_str = "server_port";
492 	int i, parseOk;
493 
494 	(void)maxpacketsize;
495 
496 	tc = (struct tcphdr *)ip_next(pip);
497 	hlen = (pip->ip_hl + tc->th_off) << 2;
498 	tlen = ntohs(pip->ip_len);
499 	dlen = tlen - hlen;
500 
501 	data = (char *)pip;
502 	data += hlen;
503 
504 	/* When aliasing a client, check for the SETUP request */
505 	if ((ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_1) ||
506 	    (ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_2)) {
507 
508 		if (dlen >= (int)strlen(setup)) {
509 			if (memcmp(data, setup, strlen(setup)) == 0) {
510 				alias_rtsp_out(la, pip, lnk, data, client_port_str);
511 				return;
512 			}
513 		}
514 		if (dlen >= (int)strlen(pna)) {
515 			if (memcmp(data, pna, strlen(pna)) == 0) {
516 				alias_pna_out(la, pip, lnk, data, dlen);
517 			}
518 		}
519 	} else {
520 
521 		/*
522 		 * When aliasing a server, check for the 200 reply
523 		 * Accommodate varying number of blanks between 200 & OK
524 		 */
525 
526 		if (dlen >= (int)strlen(str200)) {
527 
528 			for (parseOk = 0, i = 0;
529 			    i <= dlen - (int)strlen(str200);
530 			    i++) {
531 				if (memcmp(&data[i], str200, strlen(str200)) == 0) {
532 					parseOk = 1;
533 					break;
534 				}
535 			}
536 			if (parseOk) {
537 
538 				i += strlen(str200);	/* skip string found */
539 				while (data[i] == ' ')	/* skip blank(s) */
540 					i++;
541 
542 				if ((dlen - i) >= (int)strlen(okstr)) {
543 
544 					if (memcmp(&data[i], okstr, strlen(okstr)) == 0)
545 						alias_rtsp_out(la, pip, lnk, data, server_port_str);
546 
547 				}
548 			}
549 		}
550 	}
551 }
552