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