X-Git-Url: http://git.jankratochvil.net/?a=blobdiff_plain;f=src%2Fnetwork.c;h=40c2bf87cffd2e3b3e93d464ee02feb9d17545c3;hb=bc907812a9679456dd5947b61f4358b3b8e386ce;hp=e39dfc18a538264362f06179ec8f4160261d5219;hpb=75d48312f938ebe055459e9be96cdd1c3c4279be;p=udpgate.git diff --git a/src/network.c b/src/network.c index e39dfc1..40c2bf8 100644 --- a/src/network.c +++ b/src/network.c @@ -35,38 +35,66 @@ #include #include #include +#include #include "network.h" #include "packet.h" +#include "main.h" /* for optarg_verbose */ +#include "pathname.h" /* Config: */ -#define NETWORK_PATHNAME_PID "/var/run/udpgate.pid" -#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 SERVER_INADDR 0x511F02EA /* paulina.vellum.cz = 81.31.2.234; host order */ +#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 */ #define PROBE_PORT 8201 /* host order */ #define CLIENT_TIMEOUT_SEC (5*60) -#define PROBE_TIMEOUT_SEC (5) +#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,const gchar *hostip_string); +void (*network_notify_hostip)(guint32 hostip_guint32); + + +static G_CONST_RETURN gchar *pid_pathname(void) +{ +static const gchar *static_pathname; +static const char *user_val; + + if (!static_pathname) + user_val=g_get_user_name(); + return pathname_find(&static_pathname, + G_STRINGIFY(LOCALSTATEDIR) "/run",PACKAGE ".pid", + (!user_val ? NULL : g_get_tmp_dir()), + (!user_val ? NULL : udpgate_printf_alloca(".%s-%s.pid",user_val,PACKAGE)), + NULL); +} 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; +guint probe_timeout_sec_max=G_MAXUINT; +static gint port_local; /* for 'probe' resends */ static gboolean write_daemon_running(pid_t pid); @@ -79,7 +107,7 @@ int pid_int; if (sock_gsource) return getpid(); - if (!(f=fopen(NETWORK_PATHNAME_PID,"r"))) + if (!(f=fopen(pid_pathname(),"r"))) goto err; got=fgets(buf,sizeof(buf),f); fclose(f); /* FIXME: error ignored */ @@ -105,15 +133,20 @@ static gboolean write_daemon_running(pid_t pid) FILE *f; if (pid==(pid_t)-1) { - if (unlink(NETWORK_PATHNAME_PID)) { + if (unlink(pid_pathname())) { if (errno!=ENOENT) - g_warning(_("Error removing PID file \"%s\": %m"),NETWORK_PATHNAME_PID); + g_warning(_("Error removing PID file \"%s\": %m"),pid_pathname()); return FALSE; } return TRUE; } - if (!(f=fopen(NETWORK_PATHNAME_PID,"w"))) { - g_warning(_("Error writing PID %d to \"%s\": %m"),(int)pid,NETWORK_PATHNAME_PID); + if (!(f=fopen(pid_pathname(),"w"))) { +static gboolean once=TRUE; + + if (once) { + once=FALSE; + g_warning(_("Error writing PID %d to \"%s\": %m"),(int)pid,pid_pathname()); + } return FALSE; } fprintf(f,"%d\n",(int)pid); /* errors ignored */ @@ -124,11 +157,14 @@ FILE *f; 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; + if (optarg_verbose) + g_message(_("Client fd %d removed timeout id %d"),client->gpollfd.fd,client->timeout_id); errgboolean=g_source_remove(client->timeout_id); g_assert(errgboolean==TRUE); client->timeout_id=0; @@ -137,66 +173,187 @@ gboolean errgboolean; static gboolean client_touch_timeout(struct client *client); -static void client_touch(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); client->timeout_id=g_timeout_add( - (client==probe ? PROBE_TIMEOUT_SEC*1000 : CLIENT_TIMEOUT_SEC*1000), /* interval; msec */ + timeout_sec*1000, /* interval; msec */ (GSourceFunc)client_touch_timeout, /* function */ client); /* data */ + if (optarg_verbose) + g_message(_("Client fd %d new timeout id %d"),client->gpollfd.fd,client->timeout_id); g_assert(client->timeout_id!=0); } static void client_destroy(struct client *client); +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); + + /* Do not destroy the timeout in client_destroy(). + * It would crash GLib - we remove it be returning FALSE from here. + */ + g_assert(client->timeout_id!=0); + client->timeout_id=0; if (client==probe) { + if (probe_timeout_sec_nowbound==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_udp!=NULL); + + for (;;) { +char packet[0x10000]; ssize_t gotlen; struct sockaddr_in sockaddr_in_from; -char packet[0x10000]; -struct client *client; -struct sockaddr_in sockaddr_in_server; +struct client *client=NULL /* Prevent false positive: might be used uninitialized */; socklen_t sockaddr_in_from_length; - - g_return_if_fail(master!=NULL); - - sockaddr_in_from_length=sizeof(sockaddr_in_from); - while (-1!=(gotlen=recvfrom( - master->gpollfd.fd, /* s */ - packet, /* buf */ - sizeof(packet), /* len */ - 0, /* flags */ - (struct sockaddr *)&sockaddr_in_from, /* from */ - &sockaddr_in_from_length))) { /* fromlen */ GList *clientl; + sockaddr_in_from_length=sizeof(sockaddr_in_from); + if (-1==(gotlen=recvfrom( + master_udp->gpollfd.fd, /* s */ + packet, /* buf */ + sizeof(packet), /* len */ + 0, /* flags */ + (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; + + if (packet_recognized(packet,gotlen)) { + handle_master_probe(packet,gotlen,&sockaddr_in_from); + continue; + } + /* Not yet initialized by 'probe' reply - drop it. */ + if (probe) { + if (optarg_verbose) + g_message(_("Data packet received from %s but no probe reply yet; dropping packet."), + SOCKADDR_IN_TO_STRING(&sockaddr_in_from)); + continue; + } + /* 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 @@ -205,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); - 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); + client_touch(client,CLIENT_TIMEOUT_SEC); /* FIXME: errors checking */ sendto( client->gpollfd.fd, /* s */ @@ -224,86 +377,117 @@ GList *clientl; } } -static void handle_probe(struct client *probe) +static void handle_master_tcp(struct client *master_tcp) { -ssize_t gotlen; + g_return_if_fail(master_tcp!=NULL); + + for (;;) { +int sock_new; struct sockaddr_in sockaddr_in_from; -char packet[0x10000]; +struct client *client=NULL /* Prevent false positive: might be used uninitialized */; socklen_t sockaddr_in_from_length; +int connect_got; +struct client *server; - g_return_if_fail(probe!=NULL); - - sockaddr_in_from_length=sizeof(sockaddr_in_from); - while (-1!=(gotlen=recvfrom( - master->gpollfd.fd, /* s */ - packet, /* buf */ - sizeof(packet), /* len */ - 0, /* flags */ - (struct sockaddr *)&sockaddr_in_from, /* from */ - &sockaddr_in_from_length))) { /* fromlen */ -static GHashTable *got_hash; -static gpointer got_unique_gpointer; -static gpointer hostip_gpointer; -static guint32 hostip_guint32; + 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; - if (!(got_hash=packet_disassembly(packet,gotlen))) - continue; - if (!(g_hash_table_lookup_extended( - got_hash, /* hash_table */ - GUINT_TO_POINTER(PACKET_ELEM_TYPE_DATA_GUINT32), /* lookup_key */ - NULL, /* orig_key */ - &got_unique_gpointer))) { -err_packet_disassembly_destroy_got_hash: - packet_disassembly_destroy(got_hash); + + /* 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 (GPOINTER_TO_UINT(got_unique_gpointer)!=probe_unique) - goto err_packet_disassembly_destroy_got_hash; - if (!(g_hash_table_lookup_extended( - got_hash, /* hash_table */ - GUINT_TO_POINTER(PACKET_ELEM_TYPE_CLIENT_INADDR), /* lookup_key */ - NULL, /* orig_key */ - &hostip_gpointer))) - goto err_packet_disassembly_destroy_got_hash; - hostip_guint32=GPOINTER_TO_UINT(hostip_gpointer); - packet_disassembly_destroy(got_hash); - client_destroy(probe); - if (network_notify_hostip) - (*network_notify_hostip)(hostip_guint32, - udpgate_printf_alloca("%d.%d.%d.%d", - (hostip_guint32>>24U)&0xFFU, - (hostip_guint32>>16U)&0xFFU, - (hostip_guint32>> 8U)&0xFFU, - (hostip_guint32>> 0U)&0xFFU)); + 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_client(struct client *client) +static void handle_probe(struct client *probe) { + g_return_if_fail(probe!=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); + if (-1==(gotlen=recvfrom( + master_udp->gpollfd.fd, /* s */ + packet, /* buf */ + sizeof(packet), /* len */ + 0, /* flags */ + (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; + + /* Probe socket should have no response; maybe some ICMP errors - ignored. */ + } +} + +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); - while (-1!=(gotlen=recvfrom( - client->gpollfd.fd, /* s */ - packet, /* buf */ - sizeof(packet), /* len */ - 0, /* flags */ - (struct sockaddr *)&sockaddr_in_from, /* from */ - &sockaddr_in_from_length))) { /* fromlen */ + for (;;) { +ssize_t gotlen; +struct sockaddr_in sockaddr_in_from; +char packet[0x10000]; +socklen_t sockaddr_in_from_length; + + sockaddr_in_from_length=sizeof(sockaddr_in_from); + if (-1==(gotlen=recvfrom( + client->gpollfd.fd, /* s */ + packet, /* buf */ + sizeof(packet), /* len */ + 0, /* flags */ + (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; - client_touch(client); + 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 */ @@ -312,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; - if (client->gpollfd.revents&SOCK_SOURCE_CHECK_REVENTS) { - /**/ if (client==master) - handle_master(client); + client->done=0; + } +retry_in: + 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_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(client); + 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 + g_assert_not_reached(); + } + goto retry_out; } return TRUE; /* the source should be kept active */ @@ -334,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; } @@ -370,14 +709,16 @@ 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) { g_source_destroy(sock_gsource); sock_gsource=NULL; } - write_daemon_running((pid_t)-1); /* unlink */ + write_daemon_running((pid_t)-1); /* unlink; errors ignored */ } static gboolean sock_gsource_new(void) @@ -409,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,0))) { - g_warning("socket(AF_INET,SOCK_DGRAM): %m"); - return NULL; - } if (ioctl(sock,FIONBIO,&oneul)) { /* non-blocking mode */ g_warning("ioctl(sock,FIONBIO,&1): %m"); close(sock); /* errors ignored */ @@ -430,24 +768,63 @@ 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); + if (optarg_verbose) + g_message(_("Client fd %d created"),client->gpollfd.fd); + 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()) return; - if (client==master) { - master=NULL; + if (optarg_verbose) + g_message(_("Client fd %d timeout id %d destroy enter"),client->gpollfd.fd,client->timeout_id); + + 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 { @@ -459,12 +836,28 @@ static void client_destroy(struct client *client) g_source_remove_poll(sock_gsource,&client->gpollfd); 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; @@ -474,8 +867,8 @@ size_t packet_length; probe_unique=g_random_int(); probe_hash=g_hash_table_new( - g_int_hash, /* hash_func */ - g_int_equal); /* key_equal_func */ + g_direct_hash, /* hash_func */ + g_direct_equal); /* key_equal_func */ g_hash_table_insert(probe_hash,GUINT_TO_POINTER(PACKET_ELEM_TYPE_CLIENT_PORT) ,GUINT_TO_POINTER(port_local)); g_hash_table_insert(probe_hash,GUINT_TO_POINTER(PACKET_ELEM_TYPE_DATA_GUINT32),GUINT_TO_POINTER(probe_unique)); packet=packet_assembly(&packet_length,probe_hash); @@ -483,56 +876,150 @@ 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; +} + +static gboolean master_start(gint port) +{ +struct sockaddr_in sockaddr_in; +uint16_t port_use; + + g_return_val_if_fail(port>=0,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; + } + 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 (!master_udp_start(port,&sockaddr_in)) + return FALSE; + if (!master_tcp_start(port,&sockaddr_in)) { + client_destroy(master_udp); + return FALSE; + } + return TRUE; +} +static gboolean probe_start(gint port) +{ + g_return_val_if_fail(port>=0,FALSE); + g_return_val_if_fail(probe==NULL,FALSE); + + /* Setup 'probe': */ + if (!(probe=client_new(CLIENT_NEW_TYPE_UDP))) + return FALSE; + port_local=port; + if (!probe_send(probe,port)) { + client_destroy(probe); + return FALSE; + } + probe_timeout_sec_now=PROBE_TIMEOUT_SEC_BASE; + client_touch(probe,probe_timeout_sec_now); /* timeout */ return TRUE; } gboolean network_start(gint port) { pid_t daemon_pid; -struct sockaddr_in sockaddr_in; 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; } - - /* Setup 'master': */ - g_assert(master==NULL); - if (!(master=client_new())) + if (!master_start(port)) { + sock_gsource_destroy(); return FALSE; - UDPGATE_MEMZERO(&sockaddr_in); - sockaddr_in.sin_family=AF_INET; - sockaddr_in.sin_port=htons(port); - sockaddr_in.sin_addr.s_addr=INADDR_ANY; - if (bind(master->gpollfd.fd,(struct sockaddr *)&sockaddr_in,sizeof(sockaddr_in))) { - g_warning("bind(sock,{AF_INET,INADDR_ANY:%d}): %m",(int)port); -err_sock_gsource_destroy: + } + if (!probe_start(port)) { sock_gsource_destroy(); return FALSE; } + write_daemon_running(getpid()); /* errors ignored */ + if (network_notify_hostip) + (*network_notify_hostip)(0); + return TRUE; +} - /* Setup 'probe': */ - if (!(probe=client_new())) - goto err_sock_gsource_destroy; - probe_send(probe,port); - client_touch(probe); /* timeout */ +gboolean optarg_port_set_string(const gchar *port_string) +{ +char *endp; +long port_long; + + g_return_val_if_fail(port_string!=NULL,FALSE); - write_daemon_running(getpid()); + port_long=strtol(port_string,&endp,0); + if (endp && *endp) { + g_warning(_("Invalid port specification, offending string: %s"),endp); + return FALSE; + } + if (port_long<1 || port_long>=G_MAXINT || (endp && *endp)) { + g_warning(_("Invalid port integer number specification (%ld)"),port_long); + return FALSE; + } + optarg_port=port_long; return TRUE; } @@ -547,15 +1034,61 @@ int errno_save; } if (daemon_pid==getpid()) { sock_gsource_destroy(); - return TRUE; + goto ok; } errno=0; kill(daemon_pid,SIGKILL); errno_save=errno; + write_daemon_running((pid_t)-1); /* unlink; errors ignored */ if (errno_save) { g_warning(udpgate_printf_alloca(_("Unable to stop the daemon at PID %d: %s"), (int)daemon_pid,strerror(errno_save))); return FALSE; } +ok: + if (network_notify_hostip) + (*network_notify_hostip)(0); return TRUE; } + +static GMainLoop *gmainloop; +static void network_detach_network_notify_hostip(guint32 hostip_guint32) +{ + if (!hostip_guint32) + g_main_loop_quit(gmainloop); +} + +gboolean network_detach(void) +{ +pid_t daemon_pid,forked_pid; + + if ((pid_t)-1==(daemon_pid=is_daemon_running())) + return TRUE; + if (getpid()!=daemon_pid) + return TRUE; + if (!optarg_no_fork) { + if ((pid_t)-1==(forked_pid=fork())) { + g_warning("fork(2): %m"); + return FALSE; + } + if (forked_pid) { + /* parent */ + return TRUE; + } + write_daemon_running(getpid()); /* errors ignored */ + optarg_verbose=0; + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + setpgrp(); + setsid(); + } + + network_notify_hostip=network_detach_network_notify_hostip; + gmainloop=g_main_loop_new( + NULL, /* context */ + TRUE); /* is_running; ignored */ + g_main_loop_run(gmainloop); /* loop */ + /* Unable to contact the server, aborting. */ + return FALSE; +}