Skip to content

Commit d9d64c1

Browse files
committed
visualization: fix VIS UDP server socket bind on dual-stack ipv6
On dual-stack systems getaddrinfo() returns both ipv4 and ipv6 addresses. VIS bound the first in list. For me it was an ipv4. Client also gets both ipv4 and ipv6 addresses, but for ip ipv6 address is returnd first. As a result ipv6-only client can't can't connect to ipv4-only server. The fix changes VIS server to bind to all possible sockets. In case of bind() failure on v4+v6 stack we fall back to v6-only bind() to survive v4-only binds() that could be already present.
1 parent 753f98c commit d9d64c1

File tree

3 files changed

+106
-51
lines changed

3 files changed

+106
-51
lines changed

src/xmms/visualization/common.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,20 @@
2424
#include <xmmsc/xmmsc_visualization.h>
2525

2626
/**
27-
* The structures for a vis client
27+
* The structure for a vis server socket and related state.
28+
*/
29+
30+
typedef struct {
31+
xmms_socket_t socket;
32+
GIOChannel *socketio;
33+
34+
/* Refer back to the whole vis object. Needed for existing
35+
* clients lookup on incoming packets. */
36+
xmms_visualization_t *vis;
37+
} xmms_vis_server_t;
38+
39+
/**
40+
* The structures for a vis client.
2841
*/
2942

3043
typedef struct {
@@ -33,6 +46,7 @@ typedef struct {
3346
xmmsc_vis_udp_t udp;
3447
} transport;
3548
xmmsc_vis_transport_t type;
49+
xmms_vis_server_t *server;
3650
unsigned short format;
3751
xmmsc_vis_properties_t prop;
3852
} xmms_vis_client_t;
@@ -78,8 +92,9 @@ short fill_buffer (int16_t *dest, xmmsc_vis_properties_t* prop, int channels, in
7892
struct xmms_visualization_St {
7993
xmms_object_t object;
8094
xmms_output_t *output;
81-
xmms_socket_t socket;
82-
GIOChannel *socketio;
95+
/* Server sockets */
96+
int32_t serverc;
97+
xmms_vis_server_t *serverv;
8398

8499
GMutex clientlock;
85100
int32_t clientc;

src/xmms/visualization/object.c

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ delete_client (int32_t id)
9797
if (c->type == VIS_UNIXSHM) {
9898
cleanup_shm (&c->transport.shm);
9999
} else if (c->type == VIS_UDP) {
100-
cleanup_udp (&c->transport.udp, vis->socket);
100+
if (c->server) {
101+
cleanup_udp (&c->transport.udp, c->server->socket);
102+
}
101103
}
102104

103105
g_free (c);
@@ -116,13 +118,12 @@ xmms_visualization_new (xmms_output_t *output)
116118
g_mutex_init (&vis->clientlock);
117119
vis->clientc = 0;
118120
vis->output = output;
121+
vis->serverc = 0;
119122

120123
xmms_object_ref (output);
121124

122125
xmms_visualization_register_ipc_commands (XMMS_OBJECT (vis));
123126

124-
xmms_socket_invalidate (&vis->socket);
125-
126127
return vis;
127128
}
128129

@@ -134,6 +135,8 @@ xmms_visualization_new (xmms_output_t *output)
134135
static void
135136
xmms_visualization_destroy (xmms_object_t *object)
136137
{
138+
int32_t i;
139+
137140
XMMS_DBG ("Deactivating visualization object.");
138141

139142
xmms_object_unref (vis->output);
@@ -145,11 +148,13 @@ xmms_visualization_destroy (xmms_object_t *object)
145148
delete_client (vis->clientc - 1);
146149
}
147150

148-
if (xmms_socket_valid (vis->socket)) {
151+
for (i = 0; i < vis->serverc; i++) {
152+
xmms_vis_server_t *s = &vis->serverv[1];
149153
/* it seems there is no way to remove the watch */
150-
g_io_channel_shutdown (vis->socketio, FALSE, NULL);
151-
xmms_socket_close (vis->socket);
154+
g_io_channel_shutdown (s->socketio, FALSE, NULL);
155+
xmms_socket_close (s->socket);
152156
}
157+
g_free (vis->serverv);
153158

154159
xmms_visualization_unregister_ipc_commands ();
155160
}
@@ -214,6 +219,7 @@ xmms_visualization_client_register (xmms_visualization_t *vis, xmms_error_t *err
214219
/* do necessary initialisations here */
215220
c = get_client (id);
216221
c->type = VIS_NONE;
222+
c->server = NULL;
217223
c->format = 0;
218224
properties_init (&c->prop);
219225
}
@@ -308,7 +314,9 @@ package_write (xmms_vis_client_t *c, int32_t id, struct timeval *time, int chann
308314
if (c->type == VIS_UNIXSHM) {
309315
return write_shm (&c->transport.shm, c, id, time, channels, size, buf);
310316
} else if (c->type == VIS_UDP) {
311-
return write_udp (&c->transport.udp, c, id, time, channels, size, buf, vis->socket);
317+
if (c->server) {
318+
return write_udp (&c->transport.udp, c, id, time, channels, size, buf, c->server->socket);
319+
}
312320
}
313321
return FALSE;
314322
}

src/xmms/visualization/udp.c

Lines changed: 73 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,32 @@
1919
#include "common.h"
2020

2121
static gboolean
22-
udpwatcher (GIOChannel *src, GIOCondition cond, xmms_visualization_t *vis)
22+
udpwatcher (GIOChannel *src, GIOCondition cond, xmms_vis_server_t *s)
2323
{
2424
struct sockaddr_storage from;
2525
socklen_t sl = sizeof (from);
2626
xmmsc_vis_udp_timing_t packet_d;
2727
char* packet = packet_init_timing (&packet_d);
28-
if ((recvfrom (vis->socket, packet, packet_d.size, 0, (struct sockaddr *)&from, &sl)) > 0) {
28+
if ((recvfrom (s->socket, packet, packet_d.size, 0, (struct sockaddr *)&from, &sl)) > 0) {
2929
if (*packet_d.__unaligned_type == 'H') {
3030
xmms_vis_client_t *c;
3131
int32_t id;
3232

3333
XMMSC_VIS_UNALIGNED_READ (id, packet_d.__unaligned_id, int32_t);
3434
id = ntohl (id);
3535

36-
/* debug code starts
37-
char adrb[INET6_ADDRSTRLEN];
38-
struct sockaddr_in6 *a = (struct sockaddr_in6 *)&from;
39-
printf ("Client address: %s:%d, %d\n", inet_ntop (AF_INET6, &a->sin6_addr,
40-
adrb, INET6_ADDRSTRLEN), a->sin6_port, id);
41-
debug code ends */
42-
g_mutex_lock (&vis->clientlock);
36+
g_mutex_lock (&s->vis->clientlock);
4337
c = get_client (id);
4438
if (!c || c->type != VIS_UDP) {
45-
g_mutex_unlock (&vis->clientlock);
39+
g_mutex_unlock (&s->vis->clientlock);
4640
return TRUE;
4741
}
4842
/* save client address according to id */
4943
memcpy (&c->transport.udp.addr, &from, sizeof (from));
44+
c->server = s;
5045
c->transport.udp.socket[0] = 1;
5146
c->transport.udp.grace = 2000;
52-
g_mutex_unlock (&vis->clientlock);
47+
g_mutex_unlock (&s->vis->clientlock);
5348
} else if (*packet_d.__unaligned_type == 'T') {
5449
struct timeval time;
5550
xmms_vis_client_t *c;
@@ -58,15 +53,15 @@ udpwatcher (GIOChannel *src, GIOCondition cond, xmms_visualization_t *vis)
5853
XMMSC_VIS_UNALIGNED_READ (id, packet_d.__unaligned_id, int32_t);
5954
id = ntohl (id);
6055

61-
g_mutex_lock (&vis->clientlock);
56+
g_mutex_lock (&s->vis->clientlock);
6257
c = get_client (id);
6358
if (!c || c->type != VIS_UDP) {
64-
g_mutex_unlock (&vis->clientlock);
59+
g_mutex_unlock (&s->vis->clientlock);
6560
free (packet);
6661
return TRUE;
6762
}
6863
c->transport.udp.grace = 2000;
69-
g_mutex_unlock (&vis->clientlock);
64+
g_mutex_unlock (&s->vis->clientlock);
7065

7166
/* give pong */
7267
gettimeofday (&time, NULL);
@@ -90,11 +85,7 @@ udpwatcher (GIOChannel *src, GIOCondition cond, xmms_visualization_t *vis)
9085
XMMSC_VIS_UNALIGNED_WRITE (&packet_d.__unaligned_serverstamp[1],
9186
(int32_t)htonl (sts.tv_usec), int32_t);
9287

93-
sendto (vis->socket, packet, packet_d.size, 0, (struct sockaddr *)&from, sl);
94-
95-
/* new debug:
96-
printf ("Timings: local %f, remote %f, diff %f\n", tv2ts (&time), net2ts (packet_d.clientstamp), net2ts (packet_d.clientstamp) - tv2ts (&time));
97-
ends */
88+
sendto (s->socket, packet, packet_d.size, 0, (struct sockaddr *)&from, sl);
9889
} else {
9990
xmms_log_error ("Received invalid UDP package!");
10091
}
@@ -111,52 +102,93 @@ init_udp (xmms_visualization_t *vis, int32_t id, xmms_error_t *err)
111102
xmms_vis_client_t *c;
112103

113104
// setup socket if needed
114-
if (!xmms_socket_valid (vis->socket)) {
105+
// TODO: isn't there a race of multiple clients tying to bind()?
106+
if (vis->serverv == NULL) {
115107
struct addrinfo hints;
116108
struct addrinfo *result, *rp;
117-
int s;
109+
int status;
110+
111+
int32_t possible_servers = 0;
112+
int32_t opened_servers = 0;
113+
int32_t i;
114+
xmms_vis_server_t *servers = NULL;
118115

119116
memset (&hints, 0, sizeof (hints));
120117
hints.ai_family = AF_UNSPEC;
121118
hints.ai_socktype = SOCK_DGRAM;
122119
hints.ai_flags = AI_PASSIVE;
123120
hints.ai_protocol = 0;
124121

125-
if ((s = getaddrinfo (NULL, G_STRINGIFY (XMMS_DEFAULT_UDP_PORT), &hints, &result)) != 0)
122+
if ((status = getaddrinfo (NULL, G_STRINGIFY (XMMS_DEFAULT_UDP_PORT), &hints, &result)) != 0)
126123
{
127-
xmms_log_error ("Could not setup socket! getaddrinfo: %s", gai_strerror (s));
124+
xmms_log_error ("Could not setup socket! getaddrinfo: %s", gai_strerror (status));
128125
xmms_error_set (err, XMMS_ERROR_NO_SAUSAGE, "Could not setup socket!");
129126
return -1;
130127
}
131128

132129
for (rp = result; rp != NULL; rp = rp->ai_next) {
133-
vis->socket = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
134-
if (!xmms_socket_valid (vis->socket)) {
130+
possible_servers++;
131+
}
132+
servers = g_new0 (xmms_vis_server_t, possible_servers);
133+
if (servers == NULL) {
134+
xmms_log_error ("Could not allocate memory!");
135+
xmms_error_set (err, XMMS_ERROR_NO_SAUSAGE, "Could not allocate memory!");
136+
freeaddrinfo (result);
137+
return -1;
138+
}
139+
140+
for (rp = result; rp != NULL; rp = rp->ai_next) {
141+
int sock;
142+
xmms_vis_server_t *s = &servers[opened_servers];
143+
144+
sock = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
145+
if (!xmms_socket_valid (sock)) {
135146
continue;
136147
}
137-
if (bind (vis->socket, rp->ai_addr, rp->ai_addrlen) != -1) {
138-
break;
139-
} else {
140-
close (vis->socket);
148+
if (bind (sock, rp->ai_addr, rp->ai_addrlen) == -1) {
149+
/* In case we already bound v4 socket
150+
* and v6 are attempting to set up
151+
* dual-stack v4+v6. Try again v6-only
152+
* mode. */
153+
if (rp->ai_family == AF_INET6 && errno == EADDRINUSE) {
154+
int v6only = 1;
155+
if (setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof (v6only)) == -1) {
156+
close (socket);
157+
continue;
158+
}
159+
if (bind (sock, rp->ai_addr, rp->ai_addrlen) == -1) {
160+
close (socket);
161+
continue;
162+
}
163+
} else {
164+
close (socket);
165+
continue;
166+
}
141167
}
168+
opened_servers++;
169+
s->socket = sock;
142170
}
143-
if (rp == NULL) {
144-
xmms_log_error ("Could not bind socket!");
145-
xmms_error_set (err, XMMS_ERROR_NO_SAUSAGE, "Could not bind socket!");
171+
if (opened_servers == 0) {
172+
xmms_log_error ("Could not bind any vis sockets!");
173+
xmms_error_set (err, XMMS_ERROR_NO_SAUSAGE, "Could not bind any vis sockets!");
146174
freeaddrinfo (result);
147175
return -1;
148176
}
149177
freeaddrinfo (result);
150178

151-
/* register into mainloop: */
152-
/* perhaps needed, perhaps not .. #ifdef __WIN32__
153-
vis->socketio = g_io_channel_win32_new_socket (vis->socket);
154-
#else */
155-
vis->socketio = g_io_channel_unix_new (vis->socket);
156-
/*#endif */
157-
g_io_channel_set_encoding (vis->socketio, NULL, NULL);
158-
g_io_channel_set_buffered (vis->socketio, FALSE);
159-
g_io_add_watch (vis->socketio, G_IO_IN, (GIOFunc) udpwatcher, vis);
179+
for (i = 0; i < opened_servers; i++) {
180+
xmms_vis_server_t *s = &servers[i];
181+
/* register into mainloop: */
182+
// TODO: on windows
183+
// vis->socketio = g_io_channel_win32_new_socket (vis->socket);
184+
s->socketio = g_io_channel_unix_new (s->socket);
185+
s->vis = vis;
186+
g_io_channel_set_encoding (s->socketio, NULL, NULL);
187+
g_io_channel_set_buffered (s->socketio, FALSE);
188+
g_io_add_watch (s->socketio, G_IO_IN, (GIOFunc) udpwatcher, s);
189+
}
190+
vis->serverc = opened_servers;
191+
vis->serverv = servers;
160192
}
161193

162194
/* set up client structure */

0 commit comments

Comments
 (0)