From bc907812a9679456dd5947b61f4358b3b8e386ce Mon Sep 17 00:00:00 2001 From: short <> Date: Sun, 13 Nov 2005 15:09:06 +0000 Subject: [PATCH] TCP port forwarding implemented. - FIXME: Probing is not performed for the TCP port! - FIXME: 'struct client' is ugly. --- src/network.c | 444 +++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 392 insertions(+), 52 deletions(-) diff --git a/src/network.c b/src/network.c index 07a3061..40c2bf8 100644 --- a/src/network.c +++ b/src/network.c @@ -44,8 +44,7 @@ /* Config: */ -#define SOCK_SOURCE_CHECK_EVENTS (G_IO_IN) /* |G_IO_PRI */ -#define SOCK_SOURCE_CHECK_REVENTS (SOCK_SOURCE_CHECK_EVENTS) /* |G_IO_ERR|G_IO_HUP|G_IO_NVAL */ +#define SOCK_SOURCE_CHECK_REVENTS (G_IO_IN|G_IO_OUT) /* |G_IO_ERR|G_IO_HUP|G_IO_NVAL */ #define SERVER_INADDR 0xC37AD054 /* mms2.org = 195.122.208.84; host order */ #define SERVER_PORT 9201 /* host order */ #define PROBE_INADDR SERVER_INADDR /* host order */ @@ -53,6 +52,8 @@ #define CLIENT_TIMEOUT_SEC (5*60) #define PROBE_TIMEOUT_SEC_BASE 5 #define PROBE_TIMEOUT_SEC_MAX 3500 +#define TCP_BACKLOG 5 +#define GSTRING_TO_SEND_MAX 1500*6 /* maximum data buffered per TCP client */ void (*network_notify_hostip)(guint32 hostip_guint32); @@ -77,12 +78,18 @@ struct client { GPollFD gpollfd; struct sockaddr_in sockaddr_in_from; guint timeout_id; + struct client *bound; + unsigned connect_pending:1; /* for TCP connection to the server; activated by: G_IO_OUT */ + unsigned server_only:1; /* nonimportant connect - the server side */ + unsigned done:1; /* temporary marker */ + GString *gstring_to_send; /* for TCP connections; data to be sent to this socket */ }; static GSource *sock_gsource; static GList *sock_client_list; /* of 'struct client *', incl. 'master' and 'probe' */ -static struct client *master; /* no 'timeout' permitted */ +static struct client *master_udp; /* no 'timeout' permitted */ +static struct client *master_tcp; /* no 'timeout' permitted */ static struct client *probe; /* 'timeout' permitted */ static guint32 probe_unique; static guint probe_timeout_sec_now; @@ -150,7 +157,8 @@ static gboolean once=TRUE; static void client_timeout_remove(struct client *client) { g_return_if_fail(client!=NULL); - g_return_if_fail(client!=master); + g_return_if_fail(client!=master_udp); + g_return_if_fail(client!=master_tcp); if (client->timeout_id) { gboolean errgboolean; @@ -168,7 +176,8 @@ static gboolean client_touch_timeout(struct client *client); static void client_touch(struct client *client,guint timeout_sec) { g_return_if_fail(client!=NULL); - g_return_if_fail(client!=master); + g_return_if_fail(client!=master_udp); + g_return_if_fail(client!=master_tcp); g_return_if_fail(timeout_sec>0); client_timeout_remove(client); @@ -187,7 +196,8 @@ static gboolean probe_send(struct client *probe,gint port_local); static gboolean client_touch_timeout(struct client *client) { g_return_val_if_fail(client!=NULL,FALSE); /* FALSE=>should be removed */ - g_return_val_if_fail(client!=master,FALSE); /* FALSE=>should be removed */ + g_return_val_if_fail(client!=master_udp,FALSE); /* FALSE=>should be removed */ + g_return_val_if_fail(client!=master_tcp,FALSE); /* FALSE=>should be removed */ if (optarg_verbose) g_message(_("Client fd %d timeout id %d occured/entered"),client->gpollfd.fd,client->timeout_id); @@ -264,24 +274,60 @@ err_packet_disassembly_destroy_got_hash: (*network_notify_hostip)(hostip_guint32); } -static struct client *client_new(void); +enum client_new_type { + CLIENT_NEW_TYPE_UDP, + CLIENT_NEW_TYPE_TCP, + }; + +static struct client *client_new(enum client_new_type type); +static struct client *client_new_from_sock(int sock); + +static void clients_bind(struct client *client0,struct client *client1) +{ + g_return_if_fail(client0!=NULL); + g_return_if_fail(client0->bound==NULL); + g_return_if_fail(client0->gstring_to_send==NULL); + g_return_if_fail(client1!=NULL); + g_return_if_fail(client1->bound==NULL); + g_return_if_fail(client1->gstring_to_send==NULL); + g_return_if_fail(client0!=client1); + + client0->bound=client1; + client1->bound=client0; + + client0->gstring_to_send=g_string_sized_new(GSTRING_TO_SEND_MAX); + client1->gstring_to_send=g_string_sized_new(GSTRING_TO_SEND_MAX); +} + +static void client_split(struct client *client) +{ + g_return_if_fail(client!=NULL); + g_return_if_fail(client->bound!=NULL); + g_return_if_fail(client->bound!=client); + g_return_if_fail(client->bound->bound==client); + g_return_if_fail(client->gstring_to_send!=NULL); + + client->bound->bound=NULL; + client->bound=NULL; +} -static void handle_master(struct client *master) +static struct sockaddr_in sockaddr_in_server; + +static void handle_master_udp(struct client *master_udp) { - g_return_if_fail(master!=NULL); + g_return_if_fail(master_udp!=NULL); for (;;) { char packet[0x10000]; ssize_t gotlen; struct sockaddr_in sockaddr_in_from; struct client *client=NULL /* Prevent false positive: might be used uninitialized */; -struct sockaddr_in sockaddr_in_server; socklen_t sockaddr_in_from_length; GList *clientl; sockaddr_in_from_length=sizeof(sockaddr_in_from); if (-1==(gotlen=recvfrom( - master->gpollfd.fd, /* s */ + master_udp->gpollfd.fd, /* s */ packet, /* buf */ sizeof(packet), /* len */ 0, /* flags */ @@ -307,7 +353,7 @@ GList *clientl; /* FIXME: Performance: Ugly search... */ for (clientl=sock_client_list;clientl;clientl=clientl->next) { client=clientl->data; - if (client==master) + if (client==master_udp) continue; if (1 && client->sockaddr_in_from.sin_family ==sockaddr_in_from.sin_family @@ -316,14 +362,10 @@ GList *clientl; break; } if (!clientl) { - client=client_new(); + client=client_new(CLIENT_NEW_TYPE_UDP); client->sockaddr_in_from=sockaddr_in_from; } client_touch(client,CLIENT_TIMEOUT_SEC); - UDPGATE_MEMZERO(&sockaddr_in_server); - sockaddr_in_server.sin_family=AF_INET; - sockaddr_in_server.sin_port=htons(SERVER_PORT); - sockaddr_in_server.sin_addr.s_addr=htonl(SERVER_INADDR); /* FIXME: errors checking */ sendto( client->gpollfd.fd, /* s */ @@ -335,6 +377,65 @@ GList *clientl; } } +static void handle_master_tcp(struct client *master_tcp) +{ + g_return_if_fail(master_tcp!=NULL); + + for (;;) { +int sock_new; +struct sockaddr_in sockaddr_in_from; +struct client *client=NULL /* Prevent false positive: might be used uninitialized */; +socklen_t sockaddr_in_from_length; +int connect_got; +struct client *server; + + sockaddr_in_from_length=sizeof(sockaddr_in_from); + if (-1==(sock_new=accept( + master_tcp->gpollfd.fd, /* s */ + (struct sockaddr *)&sockaddr_in_from, /* from */ + &sockaddr_in_from_length))) /* fromlen */ + break; + + if (sockaddr_in_from_length!=sizeof(sockaddr_in_from)) /* FIXME: errors reporting */ + continue; + + /* Not yet initialized by 'probe' reply - drop it. */ + if (probe) { + if (optarg_verbose) + g_message(_("Incoming connection received from %s but no probe reply yet; dropping connection."), + SOCKADDR_IN_TO_STRING(&sockaddr_in_from)); + close(sock_new); /* FIXME: errors reporting */ + continue; + } + + if (!(client=client_new_from_sock(sock_new))) + continue; + client->sockaddr_in_from=sockaddr_in_from; + + if (!(server=client_new(CLIENT_NEW_TYPE_TCP))) { + client_destroy(client); + continue; + } + server->server_only=1; + server->sockaddr_in_from=sockaddr_in_server; + clients_bind(client,server); + /* Currently only one of 'client' or 'server' needs to be destroyed. */ + + errno=0; + connect_got=connect( + server->gpollfd.fd, /* sockfd */ + (struct sockaddr *)&sockaddr_in_server, /* to */ + sizeof(sockaddr_in_server)); /* tolen */ + if (connect_got!=-1 || errno!=EINPROGRESS) { + g_warning(_("Error establishing server TCP connection: %m")); + client_destroy(server); + continue; + } + g_assert(!server->connect_pending); + server->connect_pending=1; + } +} + static void handle_probe(struct client *probe) { g_return_if_fail(probe!=NULL); @@ -347,7 +448,7 @@ socklen_t sockaddr_in_from_length; sockaddr_in_from_length=sizeof(sockaddr_in_from); if (-1==(gotlen=recvfrom( - master->gpollfd.fd, /* s */ + master_udp->gpollfd.fd, /* s */ packet, /* buf */ sizeof(packet), /* len */ 0, /* flags */ @@ -361,15 +462,15 @@ socklen_t sockaddr_in_from_length; } } -static void handle_client(struct client *client) +static void handle_client_udp(struct client *client) { g_return_if_fail(client!=NULL); - g_return_if_fail(master!=NULL); + g_return_if_fail(master_udp!=NULL); for (;;) { ssize_t gotlen; struct sockaddr_in sockaddr_in_from; -char packet [0x10000]; +char packet[0x10000]; socklen_t sockaddr_in_from_length; sockaddr_in_from_length=sizeof(sockaddr_in_from); @@ -386,7 +487,7 @@ socklen_t sockaddr_in_from_length; client_touch(client,CLIENT_TIMEOUT_SEC); /* FIXME: errors checking */ sendto( - master->gpollfd.fd, /* s */ + master_udp->gpollfd.fd, /* s */ packet, /* msg */ gotlen, /* len */ 0, /* flags */ @@ -395,21 +496,156 @@ socklen_t sockaddr_in_from_length; } } +static void handle_client_tcp_connect(struct client *server) +{ + g_return_if_fail(server!=NULL); + g_return_if_fail(server->connect_pending); + g_return_if_fail(master_tcp!=NULL); + + errno=0; + if (connect( + server->gpollfd.fd, /* sockfd */ + (struct sockaddr *)&sockaddr_in_server, /* to */ + sizeof(sockaddr_in_server))) { /* tolen */ + g_warning(_("Error establishing server TCP connection: %m")); + client_destroy(server); + return; + } + g_assert(server->connect_pending); + server->connect_pending=0; +} + +/* Read from client->gpollfd.fd the data for client->bound->gstring_to_send. */ +static void handle_client_tcp_read(struct client *client) +{ + g_return_if_fail(client!=NULL); + g_return_if_fail(client->bound!=NULL); + g_return_if_fail(!client->connect_pending); + g_return_if_fail(master_tcp!=NULL); + + for (;;) { +ssize_t gotlen; +size_t read_max; +char buf[GSTRING_TO_SEND_MAX]; + + read_max=sizeof(buf)-client->bound->gstring_to_send->len; + g_assert(read_max>=0); + if (read_max<=0) /* In fact it initially should not happen, there would be no: G_IO_IN */ + break; + gotlen=read(client->gpollfd.fd,buf,read_max); + if (gotlen==-1) { + if (errno==EAGAIN) + break; + g_warning(_("Error reading data from %s"),SOCKADDR_IN_TO_STRING(&client->sockaddr_in_from)); + client_destroy(client); + return; + } + if (gotlen==0) { + /* Successful close; leave the bound peer's 'gstring_to_send' to be written there. */ + if (client->server_only && client->bound->gstring_to_send->len) + client_split(client); + client_destroy(client); + return; + } + g_assert(gotlen>0); + g_assert((size_t)gotlen<=read_max); + g_string_append_len(client->bound->gstring_to_send,buf,gotlen); + } +} + +/* Write to client->gpollfd.fd the data from client->gstring_to_send. */ +static void handle_client_tcp_write(struct client *client) +{ + g_return_if_fail(client!=NULL); + g_return_if_fail(!client->connect_pending); + g_return_if_fail(master_tcp!=NULL); + + for (;;) { +ssize_t gotlen; + + if (client->gstring_to_send->len<=0) /* In fact it initially should not happen, there would be no: G_IO_OUT */ + break; + gotlen=write(client->gpollfd.fd,client->gstring_to_send->str,client->gstring_to_send->len); + if (gotlen==-1) { + if (errno==EAGAIN) + break; + g_warning(_("Error writing data to %s"),SOCKADDR_IN_TO_STRING(&client->sockaddr_in_from)); + client_destroy(client); + return; + } + if (gotlen<=0) + break; + g_assert((size_t)gotlen<=client->gstring_to_send->len); + g_string_erase(client->gstring_to_send,0,gotlen); + } + + /* Nothing left to do for such client. */ + if (!client->gstring_to_send->len && !client->bound) + client_destroy(client); +} + static gboolean sock_source_callback(gpointer data /* unused */) { GList *clientl; + /* FIXME: Quadratic 'done' marking to not to stick on stale entry! */ + + for (clientl=sock_client_list;clientl;clientl=clientl->next) { +struct client *client=clientl->data; + + client->done=0; + } +retry_in: for (clientl=sock_client_list;clientl;clientl=clientl->next) { struct client *client=clientl->data; - if (client->gpollfd.revents&SOCK_SOURCE_CHECK_REVENTS) { - /**/ if (client==master) - handle_master(client); + if (client->done) + continue; + client->done=1; + if (client->gpollfd.revents&G_IO_IN) { + /**/ if (client==master_udp) + handle_master_udp(client); + else if (client==master_tcp) + handle_master_tcp(client); else if (client==probe) handle_probe(client); + else if (client->connect_pending) + g_assert_not_reached(); + else if (client->bound) + handle_client_tcp_read(client); + else + handle_client_udp(client); + } + goto retry_in; + } + + for (clientl=sock_client_list;clientl;clientl=clientl->next) { +struct client *client=clientl->data; + + client->done=0; + } +retry_out: + for (clientl=sock_client_list;clientl;clientl=clientl->next) { +struct client *client=clientl->data; + + if (client->done) + continue; + client->done=1; + if (client->gpollfd.revents&G_IO_OUT) { + /**/ if (client==master_udp) + g_assert_not_reached(); + else if (client==master_tcp) + g_assert_not_reached(); + else if (client==probe) + g_assert_not_reached(); + else if (client->connect_pending) + handle_client_tcp_connect(client); + else if (client->gstring_to_send) + handle_client_tcp_write(client); else - handle_client(client); + g_assert_not_reached(); } + goto retry_out; } return TRUE; /* the source should be kept active */ @@ -417,8 +653,28 @@ struct client *client=clientl->data; static gboolean sock_source_prepare(GSource *source,gint *timeout) { +GList *clientl; + *timeout=-1; + for (clientl=sock_client_list;clientl;clientl=clientl->next) { +struct client *client=clientl->data; + + client->gpollfd.events=G_IO_IN; + if (!client->bound && client->gstring_to_send) + client->gpollfd.events&=~G_IO_IN; + if (client->bound && client->bound->gstring_to_send->len>=GSTRING_TO_SEND_MAX) + client->gpollfd.events&=~G_IO_IN; + if (client->connect_pending) + client->gpollfd.events&=~G_IO_IN; + if (client->bound && client->bound->connect_pending) + client->gpollfd.events&=~G_IO_IN; + if (client->connect_pending) + client->gpollfd.events|=G_IO_OUT; + if (client->gstring_to_send && client->gstring_to_send->len>0) + client->gpollfd.events|=G_IO_OUT; + g_assert(!(client->gpollfd.events&~SOCK_SOURCE_CHECK_REVENTS)); + } return FALSE; } @@ -453,7 +709,8 @@ static void sock_gsource_destroy(void) while (sock_client_list) client_destroy(sock_client_list->data); - g_assert(master==NULL); + g_assert(master_udp==NULL); + g_assert(master_tcp==NULL); g_assert(probe==NULL); if (sock_gsource) { @@ -493,19 +750,16 @@ static gboolean sock_gsource_new(void) return TRUE; } -static struct client *client_new(void) +static struct client *client_new_from_sock(int sock) { struct client *client; static unsigned long oneul=1; -int sock; + + g_return_val_if_fail(sock>=0,NULL); if (!sock_gsource_new()) return FALSE; - if (-1==(sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))) { - g_warning("socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP): %m"); - return NULL; - } if (ioctl(sock,FIONBIO,&oneul)) { /* non-blocking mode */ g_warning("ioctl(sock,FIONBIO,&1): %m"); close(sock); /* errors ignored */ @@ -514,9 +768,13 @@ int sock; udpgate_new(client); client->gpollfd.fd=sock; - client->gpollfd.events=SOCK_SOURCE_CHECK_EVENTS; - client->gpollfd.revents=0; + client->gpollfd.events=0; /* Initialized later. */ + client->gpollfd.revents=0; /* Probably does not need to be initialized. */ client->timeout_id=0; + client->bound=NULL; + client->connect_pending=0; + client->server_only=0; + client->gstring_to_send=0; sock_client_list=g_list_prepend(sock_client_list,client); g_source_add_poll(sock_gsource,&client->gpollfd); @@ -526,8 +784,33 @@ int sock; return client; } +static struct client *client_new(enum client_new_type type) +{ +int sock; + + switch (type) { + case CLIENT_NEW_TYPE_UDP: + if (-1==(sock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP))) { + g_warning("socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP): %m"); + return NULL; + } + break; + case CLIENT_NEW_TYPE_TCP: + if (-1==(sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))) { + g_warning("socket(PF_INET,SOCK_STREAM,IPPROTO_TCP): %m"); + return NULL; + } + break; + default: g_assert_not_reached(); + } + + return client_new_from_sock(sock); +} + static void client_destroy(struct client *client) { +struct client *bound; + g_return_if_fail(client!=NULL); if (!sock_gsource_new()) @@ -536,8 +819,12 @@ static void client_destroy(struct client *client) if (optarg_verbose) g_message(_("Client fd %d timeout id %d destroy enter"),client->gpollfd.fd,client->timeout_id); - if (client==master) { - master=NULL; + if (client==master_udp) { + master_udp=NULL; + g_assert(client->timeout_id==0); + } + else if (client==master_tcp) { + master_tcp=NULL; g_assert(client->timeout_id==0); } else { @@ -550,15 +837,27 @@ static void client_destroy(struct client *client) sock_client_list=g_list_remove(sock_client_list,client); close(client->gpollfd.fd); /* errors ignored */ + if (client->gstring_to_send) + g_string_free(client->gstring_to_send,TRUE); + if (optarg_verbose) g_message(_("Client fd %d timeout id %d destroy finish"),client->gpollfd.fd,client->timeout_id); + bound=client->bound; g_free(client); + + if (bound) { + g_assert(bound!=client); + g_assert(bound->bound==client); + client->bound=NULL; + bound->bound=NULL; + client_destroy(bound); + } } static gboolean probe_send(struct client *probe,gint port_local) { -struct sockaddr_in sockaddr_in_server; +struct sockaddr_in sockaddr_in_probe; GHashTable *probe_hash; gpointer packet; size_t packet_length; @@ -577,19 +876,55 @@ size_t packet_length; if (!packet) return FALSE; - UDPGATE_MEMZERO(&sockaddr_in_server); - sockaddr_in_server.sin_family=AF_INET; - sockaddr_in_server.sin_port=htons(PROBE_PORT); - sockaddr_in_server.sin_addr.s_addr=htonl(PROBE_INADDR); + UDPGATE_MEMZERO(&sockaddr_in_probe); + sockaddr_in_probe.sin_family=AF_INET; + sockaddr_in_probe.sin_port=htons(PROBE_PORT); + sockaddr_in_probe.sin_addr.s_addr=htonl(PROBE_INADDR); /* FIXME: errors checking */ sendto( probe->gpollfd.fd, /* s */ packet, /* msg */ packet_length, /* len */ 0, /* flags */ - (struct sockaddr *)&sockaddr_in_server, /* to */ - sizeof(sockaddr_in_server)); /* tolen */ + (struct sockaddr *)&sockaddr_in_probe, /* to */ + sizeof(sockaddr_in_probe)); /* tolen */ + + return TRUE; +} + +static gboolean master_udp_start(gint port,const struct sockaddr_in *sockaddr_in_pointer) +{ + g_return_val_if_fail(master_udp==NULL,FALSE); + if (!(master_udp=client_new(CLIENT_NEW_TYPE_UDP))) + return FALSE; + if (bind(master_udp->gpollfd.fd,(struct sockaddr *)sockaddr_in_pointer,sizeof(*sockaddr_in_pointer))) { + g_warning("bind(sock_udp,{AF_INET,INADDR_ANY:%d}): %m",(int)port); + return FALSE; + } + return TRUE; +} + +static gboolean master_tcp_start(gint port,const struct sockaddr_in *sockaddr_in_pointer) +{ +static int onei=1; + + g_return_val_if_fail(master_tcp==NULL,FALSE); + + if (!(master_tcp=client_new(CLIENT_NEW_TYPE_TCP))) + return FALSE; + if (setsockopt(master_tcp->gpollfd.fd,SOL_SOCKET,SO_REUSEADDR,&onei,sizeof(onei))) { + g_warning("setsockopt(sock_tcp,SOL_SOCKET,SO_REUSEADDR,1): %m"); + return FALSE; + } + if (bind(master_tcp->gpollfd.fd,(struct sockaddr *)sockaddr_in_pointer,sizeof(*sockaddr_in_pointer))) { + g_warning("bind(sock_tcp,{AF_INET,INADDR_ANY:%d}): %m",(int)port); + return FALSE; + } + if (listen(master_tcp->gpollfd.fd,TCP_BACKLOG)) { + g_warning("listen(sock_tcp,%d): %m",TCP_BACKLOG); + return FALSE; + } return TRUE; } @@ -599,23 +934,23 @@ struct sockaddr_in sockaddr_in; uint16_t port_use; g_return_val_if_fail(port>=0,FALSE); - g_return_val_if_fail(master==NULL,FALSE); + g_return_val_if_fail(master_udp==NULL,FALSE); + g_return_val_if_fail(master_tcp==NULL,FALSE); port_use=port; if (port < 0 || port_use != port) { g_warning(_("Port value %d is not valid for IPv4!"),(int)port); return FALSE; } - - /* Setup 'master': */ - if (!(master=client_new())) - return FALSE; UDPGATE_MEMZERO(&sockaddr_in); sockaddr_in.sin_family=AF_INET; sockaddr_in.sin_port=htons(port_use); sockaddr_in.sin_addr.s_addr=htonl(INADDR_ANY); - if (bind(master->gpollfd.fd,(struct sockaddr *)&sockaddr_in,sizeof(sockaddr_in))) { - g_warning("bind(sock,{AF_INET,INADDR_ANY:%u}): %m",(unsigned)port_use); + + if (!master_udp_start(port,&sockaddr_in)) + return FALSE; + if (!master_tcp_start(port,&sockaddr_in)) { + client_destroy(master_udp); return FALSE; } return TRUE; @@ -627,7 +962,7 @@ static gboolean probe_start(gint port) g_return_val_if_fail(probe==NULL,FALSE); /* Setup 'probe': */ - if (!(probe=client_new())) + if (!(probe=client_new(CLIENT_NEW_TYPE_UDP))) return FALSE; port_local=port; if (!probe_send(probe,port)) { @@ -645,6 +980,11 @@ pid_t daemon_pid; g_return_val_if_fail(port>=0,FALSE); + UDPGATE_MEMZERO(&sockaddr_in_server); + sockaddr_in_server.sin_family=AF_INET; + sockaddr_in_server.sin_port=htons(SERVER_PORT); + sockaddr_in_server.sin_addr.s_addr=htonl(SERVER_INADDR); + if ((pid_t)-1!=(daemon_pid=is_daemon_running())) { g_warning(_("Cannot start network daemon: Daemon is already running on PID %d"),(int)daemon_pid); return FALSE; -- 1.8.3.1