11573Srgrimes/*
21573Srgrimes * alias_smedia.c
31573Srgrimes *
41573Srgrimes * Copyright (c) 2000 Whistle Communications, Inc.
51573Srgrimes * All rights reserved.
61573Srgrimes *
71573Srgrimes * Subject to the following obligations and disclaimer of warranty, use and
81573Srgrimes * redistribution of this software, in source or object code forms, with or
91573Srgrimes * without modifications are expressly permitted by Whistle Communications;
101573Srgrimes * provided, however, that:
111573Srgrimes * 1. Any and all reproductions of the source or object code must include the
121573Srgrimes *    copyright notice above and the following disclaimer of warranties; and
131573Srgrimes * 2. No rights are granted, in any manner or form, to use Whistle
141573Srgrimes *    Communications, Inc. trademarks, including the mark "WHISTLE
151573Srgrimes *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
161573Srgrimes *    such appears in the above copyright notice or in the software.
171573Srgrimes *
181573Srgrimes * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
191573Srgrimes * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
201573Srgrimes * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
211573Srgrimes * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
221573Srgrimes * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
231573Srgrimes * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
241573Srgrimes * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
251573Srgrimes * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
261573Srgrimes * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
271573Srgrimes * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
281573Srgrimes * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
291573Srgrimes * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
301573Srgrimes * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
311573Srgrimes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
321573Srgrimes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
331573Srgrimes * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
341573Srgrimes * OF SUCH DAMAGE.
351573Srgrimes *
361573Srgrimes * Copyright (c) 2000  Junichi SATOH <junichi@astec.co.jp>
371573Srgrimes *                                   <junichi@junichi.org>
381573Srgrimes * All rights reserved.
391573Srgrimes *
4092986Sobrien * Redistribution and use in source and binary forms, with or without
4192986Sobrien * modification, are permitted provided that the following conditions
421573Srgrimes * are met:
431573Srgrimes * 1. Redistributions of source code must retain the above copyright
441573Srgrimes *    notice, this list of conditions and the following disclaimer.
451573Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
4692905Sobrien *    notice, this list of conditions and the following disclaimer in the
4792905Sobrien *    documentation and/or other materials provided with the distribution.
4892905Sobrien *
491573Srgrimes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
501573Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
511573Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
5261218Sache * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
531573Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
541573Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
551573Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
561573Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
5757035Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
5857035Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
5957035Sobrien * SUCH DAMAGE.
6057035Sobrien *
6157035Sobrien * Authors: Erik Salander <erik@whistle.com>
6257035Sobrien *          Junichi SATOH <junichi@astec.co.jp>
6357035Sobrien *                        <junichi@junichi.org>
6457035Sobrien */
6557035Sobrien
6657035Sobrien#include <sys/cdefs.h>
6757035Sobrien__FBSDID("$FreeBSD$");
6857035Sobrien
6957035Sobrien/*
7057035Sobrien   Alias_smedia.c is meant to contain the aliasing code for streaming media
7157035Sobrien   protocols.  It performs special processing for RSTP sessions under TCP.
7257035Sobrien   Specifically, when a SETUP request is sent by a client, or a 200 reply
7357035Sobrien   is sent by a server, it is intercepted and modified.  The address is
7457035Sobrien   changed to the gateway machine and an aliasing port is used.
7557035Sobrien
7657035Sobrien   More specifically, the "client_port" configuration parameter is
7757035Sobrien   parsed for SETUP requests.  The "server_port" configuration parameter is
7857035Sobrien   parsed for 200 replies eminating from a server.  This is intended to handle
7957035Sobrien   the unicast case.
8057035Sobrien
8157035Sobrien   RTSP also allows a redirection of a stream to another client by using the
8257035Sobrien   "destination" configuration parameter.  The destination config parm would
8357035Sobrien   indicate a different IP address.  This function is NOT supported by the
8457035Sobrien   RTSP translation code below.
8557035Sobrien
8657035Sobrien   The RTSP multicast functions without any address translation intervention.
8757035Sobrien
8857035Sobrien   For this routine to work, the SETUP/200 must fit entirely
8957035Sobrien   into a single TCP packet.  This is typically the case, but exceptions
9057035Sobrien   can easily be envisioned under the actual specifications.
9157035Sobrien
9257035Sobrien   Probably the most troubling aspect of the approach taken here is
9357035Sobrien   that the new SETUP/200 will typically be a different length, and
9457035Sobrien   this causes a certain amount of bookkeeping to keep track of the
9557035Sobrien   changes of sequence and acknowledgment numbers, since the client
9657035Sobrien   machine is totally unaware of the modification to the TCP stream.
9757035Sobrien
9857035Sobrien   Initial version:  May, 2000 (eds)
9957035Sobrien*/
10057035Sobrien
10157035Sobrien#ifdef _KERNEL
10257035Sobrien#include <sys/param.h>
10357035Sobrien#include <sys/systm.h>
10457035Sobrien#include <sys/kernel.h>
10557035Sobrien#include <sys/module.h>
10657035Sobrien#else
10757035Sobrien#include <errno.h>
10857035Sobrien#include <sys/types.h>
10957035Sobrien#include <stdio.h>
11057035Sobrien#include <string.h>
11157035Sobrien#endif
11257035Sobrien
11357035Sobrien#include <netinet/in_systm.h>
11457035Sobrien#include <netinet/in.h>
11557035Sobrien#include <netinet/ip.h>
11657035Sobrien#include <netinet/tcp.h>
11757035Sobrien
11857035Sobrien#ifdef _KERNEL
11957035Sobrien#include <netinet/libalias/alias.h>
12057035Sobrien#include <netinet/libalias/alias_local.h>
12157035Sobrien#include <netinet/libalias/alias_mod.h>
12257035Sobrien#else
12357035Sobrien#include "alias_local.h"
12457035Sobrien#include "alias_mod.h"
12557035Sobrien#endif
12657035Sobrien
12757035Sobrien#define RTSP_CONTROL_PORT_NUMBER_1 554
12857035Sobrien#define RTSP_CONTROL_PORT_NUMBER_2 7070
12957035Sobrien#define TFTP_PORT_NUMBER 69
13057035Sobrien
13157035Sobrienstatic void
13257035SobrienAliasHandleRtspOut(struct libalias *, struct ip *, struct alias_link *,
13357035Sobrien		  int maxpacketsize);
13457035Sobrienstatic int
13557035Sobrienfingerprint(struct libalias *la, struct alias_data *ah)
13657035Sobrien{
13757035Sobrien
13857035Sobrien	if (ah->dport != NULL && ah->aport != NULL && ah->sport != NULL &&
13957035Sobrien            ntohs(*ah->dport) == TFTP_PORT_NUMBER)
14057035Sobrien		return (0);
14157035Sobrien	if (ah->dport == NULL || ah->sport == NULL || ah->lnk == NULL ||
14257035Sobrien	    ah->maxpktsize == 0)
14357035Sobrien		return (-1);
14457035Sobrien	if (ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_1
14557035Sobrien	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_1
14657035Sobrien	    || ntohs(*ah->dport) == RTSP_CONTROL_PORT_NUMBER_2
14757035Sobrien	    || ntohs(*ah->sport) == RTSP_CONTROL_PORT_NUMBER_2)
14857035Sobrien		return (0);
14957035Sobrien	return (-1);
15057035Sobrien}
15157035Sobrien
15257035Sobrienstatic int
15357035Sobrienprotohandler(struct libalias *la, struct ip *pip, struct alias_data *ah)
15457035Sobrien{
15557035Sobrien
15657035Sobrien	if (ntohs(*ah->dport) == TFTP_PORT_NUMBER)
15757035Sobrien		FindRtspOut(la, pip->ip_src, pip->ip_dst,
15857035Sobrien 			    *ah->sport, *ah->aport, IPPROTO_UDP);
15957035Sobrien	else AliasHandleRtspOut(la, pip, ah->lnk, ah->maxpktsize);
16057035Sobrien	return (0);
16157035Sobrien}
16257035Sobrien
16357035Sobrienstruct proto_handler handlers[] = {
16457035Sobrien	{
16557035Sobrien	  .pri = 100,
16657035Sobrien	  .dir = OUT,
16757035Sobrien	  .proto = TCP|UDP,
16857035Sobrien	  .fingerprint = &fingerprint,
16957035Sobrien	  .protohandler = &protohandler
17057035Sobrien	},
17157035Sobrien	{ EOH }
17257035Sobrien};
17357035Sobrien
17457035Sobrienstatic int
17557035Sobrienmod_handler(module_t mod, int type, void *data)
17657035Sobrien{
17757035Sobrien	int error;
17857035Sobrien
17957035Sobrien	switch (type) {
18057035Sobrien	case MOD_LOAD:
18157035Sobrien		error = 0;
18257035Sobrien		LibAliasAttachHandlers(handlers);
18357035Sobrien		break;
18457035Sobrien	case MOD_UNLOAD:
1851573Srgrimes		error = 0;
1861573Srgrimes		LibAliasDetachHandlers(handlers);
1871573Srgrimes		break;
1881573Srgrimes	default:
1891573Srgrimes		error = EINVAL;
1901573Srgrimes	}
1911573Srgrimes	return (error);
1921573Srgrimes}
1931573Srgrimes
1941573Srgrimes#ifdef _KERNEL
1951573Srgrimesstatic
1961573Srgrimes#endif
1971573Srgrimesmoduledata_t alias_mod = {
1981573Srgrimes       "alias_smedia", mod_handler, NULL
1991573Srgrimes};
2001573Srgrimes
2011573Srgrimes#ifdef	_KERNEL
2021573SrgrimesDECLARE_MODULE(alias_smedia, alias_mod, SI_SUB_DRIVERS, SI_ORDER_SECOND);
2031573SrgrimesMODULE_VERSION(alias_smedia, 1);
2041573SrgrimesMODULE_DEPEND(alias_smedia, libalias, 1, 1, 1);
2051573Srgrimes#endif
2061573Srgrimes
2071573Srgrimes#define RTSP_CONTROL_PORT_NUMBER_1 554
2081573Srgrimes#define RTSP_CONTROL_PORT_NUMBER_2 7070
2091573Srgrimes#define RTSP_PORT_GROUP            2
2101573Srgrimes
2111573Srgrimes#define ISDIGIT(a) (((a) >= '0') && ((a) <= '9'))
2121573Srgrimes
2131573Srgrimesstatic int
2141573Srgrimessearch_string(char *data, int dlen, const char *search_str)
2151573Srgrimes{
2161573Srgrimes	int i, j, k;
2171573Srgrimes	int search_str_len;
2181573Srgrimes
2191573Srgrimes	search_str_len = strlen(search_str);
2201573Srgrimes	for (i = 0; i < dlen - search_str_len; i++) {
2211573Srgrimes		for (j = i, k = 0; j < dlen - search_str_len; j++, k++) {
2221573Srgrimes			if (data[j] != search_str[k] &&
2231573Srgrimes			    data[j] != search_str[k] - ('a' - 'A')) {
2241573Srgrimes				break;
2251573Srgrimes			}
2261573Srgrimes			if (k == search_str_len - 1) {
2271573Srgrimes				return (j + 1);
2281573Srgrimes			}
2291573Srgrimes		}
2301573Srgrimes	}
2311573Srgrimes	return (-1);
2321573Srgrimes}
2331573Srgrimes
2341573Srgrimesstatic int
2351573Srgrimesalias_rtsp_out(struct libalias *la, struct ip *pip,
2361573Srgrimes    struct alias_link *lnk,
2371573Srgrimes    char *data,
2381573Srgrimes    const char *port_str)
2391573Srgrimes{
2401573Srgrimes	int hlen, tlen, dlen;
2411573Srgrimes	struct tcphdr *tc;
2421573Srgrimes	int i, j, pos, state, port_dlen, new_dlen, delta;
2431573Srgrimes	u_short p[2], new_len;
2441573Srgrimes	u_short sport, eport, base_port;
2451573Srgrimes	u_short salias = 0, ealias = 0, base_alias = 0;
2461573Srgrimes	const char *transport_str = "transport:";
2471573Srgrimes	char newdata[2048], *port_data, *port_newdata, stemp[80];
2481573Srgrimes	int links_created = 0, pkt_updated = 0;
2491573Srgrimes	struct alias_link *rtsp_lnk = NULL;
2501573Srgrimes	struct in_addr null_addr;
2511573Srgrimes
2521573Srgrimes	/* Calculate data length of TCP packet */
2531573Srgrimes	tc = (struct tcphdr *)ip_next(pip);
2541573Srgrimes	hlen = (pip->ip_hl + tc->th_off) << 2;
2551573Srgrimes	tlen = ntohs(pip->ip_len);
2561573Srgrimes	dlen = tlen - hlen;
25722478Sache
25822478Sache	/* 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
430static int
431alias_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
481static void
482AliasHandleRtspOut(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		 * Accomodate 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