4 * Copyright (C) 2002-2003 Sistina Software, Inc. All rights reserved.
5 * Copyright (C) 2004-2009 Red Hat, Inc. All rights reserved.
7 * This file is part of LVM2.
9 * This copyrighted material is made available to anyone wishing to use,
10 * modify, copy, or redistribute it subject to the terms and conditions
11 * of the GNU Lesser General Public License v.2.1.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, write to the Free Software Foundation,
15 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 * This provides the inter-clvmd communications for a system without CMAN.
20 * There is a listening TCP socket which accepts new connections in the
22 * It can also make outgoing connnections to the other clvmd nodes.
26 #define _FILE_OFFSET_BITS 64
28 #include <configure.h>
30 #include <sys/types.h>
31 #include <sys/utsname.h>
32 #include <sys/ioctl.h>
33 #include <sys/socket.h>
35 #include <sys/socket.h>
36 #include <netinet/in.h>
49 #include <libdevmapper.h>
52 #include "clvmd-comms.h"
54 #include "clvmd-gulm.h"
56 #define DEFAULT_TCP_PORT 21064
58 static int listen_fd
= -1;
60 struct dm_hash_table
*sock_hash
;
62 static int get_our_ip_address(char *addr
, int *family
);
63 static int read_from_tcpsock(struct local_client
*fd
, char *buf
, int len
, char *csid
,
64 struct local_client
**new_client
);
66 /* Called by init_cluster() to open up the listening socket */
67 int init_comms(unsigned short port
)
69 struct sockaddr_in6 addr
;
71 sock_hash
= dm_hash_create(100);
72 tcp_port
= port
? : DEFAULT_TCP_PORT
;
74 listen_fd
= socket(AF_INET6
, SOCK_STREAM
, 0);
83 setsockopt(listen_fd
, SOL_SOCKET
, SO_REUSEADDR
, &one
, sizeof(int));
84 setsockopt(listen_fd
, SOL_SOCKET
, SO_KEEPALIVE
, &one
, sizeof(int));
87 memset(&addr
, 0, sizeof(addr
)); // Bind to INADDR_ANY
88 addr
.sin6_family
= AF_INET6
;
89 addr
.sin6_port
= htons(tcp_port
);
91 if (bind(listen_fd
, (struct sockaddr
*)&addr
, sizeof(addr
)) < 0)
93 DEBUGLOG("Can't bind to port: %s\n", strerror(errno
));
94 syslog(LOG_ERR
, "Can't bind to port %d, is clvmd already running ?", tcp_port
);
101 /* Set Close-on-exec */
102 fcntl(listen_fd
, F_SETFD
, 1);
107 void tcp_remove_client(const char *c_csid
)
109 struct local_client
*client
;
110 char csid
[GULM_MAX_CSID_LEN
];
112 memcpy(csid
, c_csid
, sizeof csid
);
113 DEBUGLOG("tcp_remove_client\n");
115 /* Don't actually close the socket here - that's the
116 job of clvmd.c whch will do the job when it notices the
117 other end has gone. We just need to remove the client(s) from
118 the hash table so we don't try to use it for sending any more */
119 for (i
= 0; i
< 2; i
++)
121 client
= dm_hash_lookup_binary(sock_hash
, csid
, GULM_MAX_CSID_LEN
);
124 dm_hash_remove_binary(sock_hash
, csid
, GULM_MAX_CSID_LEN
);
125 client
->removeme
= 1;
128 /* Look for a mangled one too, on the 2nd iteration. */
133 int alloc_client(int fd
, const char *c_csid
, struct local_client
**new_client
)
135 struct local_client
*client
;
136 char csid
[GULM_MAX_CSID_LEN
];
137 memcpy(csid
, c_csid
, sizeof csid
);
139 DEBUGLOG("alloc_client %d csid = %s\n", fd
, print_csid(csid
));
141 /* Create a local_client and return it */
142 client
= malloc(sizeof(struct local_client
));
145 DEBUGLOG("malloc failed\n");
149 memset(client
, 0, sizeof(struct local_client
));
151 client
->type
= CLUSTER_DATA_SOCK
;
152 client
->callback
= read_from_tcpsock
;
154 *new_client
= client
;
156 /* Add to our list of node sockets */
157 if (dm_hash_lookup_binary(sock_hash
, csid
, GULM_MAX_CSID_LEN
))
159 DEBUGLOG("alloc_client mangling CSID for second connection\n");
160 /* This is a duplicate connection but we can't close it because
161 the other end may already have started sending.
162 So, we mangle the IP address and keep it, all sending will
163 go out of the main FD
166 client
->bits
.net
.flags
= 1; /* indicate mangled CSID */
168 /* If it still exists then kill the connection as we should only
169 ever have one incoming connection from each node */
170 if (dm_hash_lookup_binary(sock_hash
, csid
, GULM_MAX_CSID_LEN
))
172 DEBUGLOG("Multiple incoming connections from node\n");
173 syslog(LOG_ERR
, " Bogus incoming connection from %d.%d.%d.%d\n", csid
[0],csid
[1],csid
[2],csid
[3]);
176 errno
= ECONNREFUSED
;
180 dm_hash_insert_binary(sock_hash
, csid
, GULM_MAX_CSID_LEN
, client
);
185 int get_main_gulm_cluster_fd()
191 /* Read on main comms (listen) socket, accept it */
192 int cluster_fd_gulm_callback(struct local_client
*fd
, char *buf
, int len
, const char *csid
,
193 struct local_client
**new_client
)
196 struct sockaddr_in6 addr
;
197 socklen_t addrlen
= sizeof(addr
);
199 char name
[GULM_MAX_CLUSTER_MEMBER_NAME_LEN
];
201 DEBUGLOG("cluster_fd_callback\n");
203 newfd
= accept(listen_fd
, (struct sockaddr
*)&addr
, &addrlen
);
205 DEBUGLOG("cluster_fd_callback, newfd=%d (errno=%d)\n", newfd
, errno
);
208 syslog(LOG_ERR
, "error in accept: %m");
210 return -1; /* Don't return an error or clvmd will close the listening FD */
213 /* Check that the client is a member of the cluster
216 if (gulm_name_from_csid((char *)&addr
.sin6_addr
, name
) < 0)
218 syslog(LOG_ERR
, "Got connect from non-cluster node %s\n",
219 print_csid((char *)&addr
.sin6_addr
));
220 DEBUGLOG("Got connect from non-cluster node %s\n",
221 print_csid((char *)&addr
.sin6_addr
));
228 status
= alloc_client(newfd
, (char *)&addr
.sin6_addr
, new_client
);
231 DEBUGLOG("cluster_fd_callback, alloc_client failed, status = %d\n", status
);
237 DEBUGLOG("cluster_fd_callback, returning %d, %p\n", newfd
, *new_client
);
241 /* Try to get at least 'len' bytes from the socket */
242 static int really_read(int fd
, char *buf
, int len
)
249 got
= read(fd
, buf
+offset
, len
-offset
);
250 DEBUGLOG("really_read. got %d bytes\n", got
);
252 } while (got
> 0 && offset
< len
);
261 static int read_from_tcpsock(struct local_client
*client
, char *buf
, int len
, char *csid
,
262 struct local_client
**new_client
)
264 struct sockaddr_in6 addr
;
265 socklen_t slen
= sizeof(addr
);
266 struct clvm_header
*header
= (struct clvm_header
*)buf
;
270 DEBUGLOG("read_from_tcpsock fd %d\n", client
->fd
);
274 getpeername(client
->fd
, (struct sockaddr
*)&addr
, &slen
);
275 memcpy(csid
, &addr
.sin6_addr
, GULM_MAX_CSID_LEN
);
277 /* Read just the header first, then get the rest if there is any.
278 * Stream sockets, sigh.
280 status
= really_read(client
->fd
, buf
, sizeof(struct clvm_header
));
285 arglen
= ntohl(header
->arglen
);
288 if (arglen
&& arglen
< GULM_MAX_CLUSTER_MESSAGE
)
290 status2
= really_read(client
->fd
, buf
+status
, arglen
);
298 DEBUGLOG("read_from_tcpsock, status = %d(errno = %d)\n", status
, errno
);
300 /* Remove it from the hash table if there's an error, clvmd will
301 remove the socket from its lists and free the client struct */
303 (status
< 0 && errno
!= EAGAIN
&& errno
!= EINTR
))
305 char remcsid
[GULM_MAX_CSID_LEN
];
307 memcpy(remcsid
, csid
, GULM_MAX_CSID_LEN
);
310 /* If the csid was mangled, then make sure we remove the right entry */
311 if (client
->bits
.net
.flags
)
313 dm_hash_remove_binary(sock_hash
, remcsid
, GULM_MAX_CSID_LEN
);
315 /* Tell cluster manager layer */
316 add_down_node(remcsid
);
319 gulm_add_up_node(csid
);
320 /* Send it back to clvmd */
321 process_message(client
, buf
, status
, csid
);
326 int gulm_connect_csid(const char *csid
, struct local_client
**newclient
)
329 struct sockaddr_in6 addr
;
333 DEBUGLOG("Connecting socket\n");
334 fd
= socket(PF_INET6
, SOCK_STREAM
, 0);
338 syslog(LOG_ERR
, "Unable to create new socket: %m");
342 addr
.sin6_family
= AF_INET6
;
343 memcpy(&addr
.sin6_addr
, csid
, GULM_MAX_CSID_LEN
);
344 addr
.sin6_port
= htons(tcp_port
);
346 DEBUGLOG("Connecting socket %d\n", fd
);
347 if (connect(fd
, (struct sockaddr
*)&addr
, sizeof(struct sockaddr_in6
)) < 0)
349 /* "Connection refused" is "normal" because clvmd may not yet be running
352 if (errno
!= ECONNREFUSED
)
354 syslog(LOG_ERR
, "Unable to connect to remote node: %m");
356 DEBUGLOG("Unable to connect to remote node: %s\n", strerror(errno
));
361 /* Set Close-on-exec */
362 fcntl(fd
, F_SETFD
, 1);
363 setsockopt(fd
, SOL_SOCKET
, SO_KEEPALIVE
, &one
, sizeof(int));
365 status
= alloc_client(fd
, csid
, newclient
);
369 add_client(*newclient
);
371 /* If we can connect to it, it must be running a clvmd */
372 gulm_add_up_node(csid
);
376 /* Send a message to a known CSID */
377 static int tcp_send_message(void *buf
, int msglen
, const char *csid
, const char *errtext
)
380 struct local_client
*client
;
381 char ourcsid
[GULM_MAX_CSID_LEN
];
385 DEBUGLOG("tcp_send_message, csid = %s, msglen = %d\n", print_csid(csid
), msglen
);
387 /* Don't connect to ourself */
388 get_our_gulm_csid(ourcsid
);
389 if (memcmp(csid
, ourcsid
, GULM_MAX_CSID_LEN
) == 0)
392 client
= dm_hash_lookup_binary(sock_hash
, csid
, GULM_MAX_CSID_LEN
);
395 status
= gulm_connect_csid(csid
, &client
);
399 DEBUGLOG("tcp_send_message, fd = %d\n", client
->fd
);
401 return write(client
->fd
, buf
, msglen
);
405 int gulm_cluster_send_message(void *buf
, int msglen
, const char *csid
, const char *errtext
)
409 DEBUGLOG("cluster send message, csid = %p, msglen = %d\n", csid
, msglen
);
411 /* If csid is NULL then send to all known (not just connected) nodes */
414 void *context
= NULL
;
415 char loop_csid
[GULM_MAX_CSID_LEN
];
417 /* Loop round all gulm-known nodes */
418 while (get_next_node_csid(&context
, loop_csid
))
420 status
= tcp_send_message(buf
, msglen
, loop_csid
, errtext
);
422 (status
< 0 && (errno
== EAGAIN
|| errno
== EINTR
)))
429 status
= tcp_send_message(buf
, msglen
, csid
, errtext
);
434 /* To get our own IP address we get the locally bound address of the
435 socket that's talking to GULM in the assumption(eek) that it will
436 be on the "right" network in a multi-homed system */
437 static int get_our_ip_address(char *addr
, int *family
)
442 get_ip_address(info
.nodename
, addr
);
447 /* Public version of above for those that don't care what protocol
449 void get_our_gulm_csid(char *csid
)
451 static char our_csid
[GULM_MAX_CSID_LEN
];
452 static int got_csid
= 0;
458 memset(our_csid
, 0, sizeof(our_csid
));
459 if (get_our_ip_address(our_csid
, &family
))
464 memcpy(csid
, our_csid
, GULM_MAX_CSID_LEN
);
467 static void map_v4_to_v6(struct in_addr
*ip4
, struct in6_addr
*ip6
)
469 ip6
->s6_addr32
[0] = 0;
470 ip6
->s6_addr32
[1] = 0;
471 ip6
->s6_addr32
[2] = htonl(0xffff);
472 ip6
->s6_addr32
[3] = ip4
->s_addr
;
475 /* Get someone else's IP address from DNS */
476 int get_ip_address(const char *node
, char *addr
)
480 memset(addr
, 0, GULM_MAX_CSID_LEN
);
482 // TODO: what do we do about multi-homed hosts ???
483 // CCSs ip_interfaces solved this but some bugger removed it.
485 /* Try IPv6 first. The man page for gethostbyname implies that
486 it will lookup ip6 & ip4 names, but it seems not to */
487 he
= gethostbyname2(node
, AF_INET6
);
490 memcpy(addr
, he
->h_addr_list
[0],
495 he
= gethostbyname2(node
, AF_INET
);
498 map_v4_to_v6((struct in_addr
*)he
->h_addr_list
[0], (struct in6_addr
*)addr
);
504 char *print_csid(const char *csid
)
506 static char buf
[128];
507 int *icsid
= (int *)csid
;
509 sprintf(buf
, "[%x.%x.%x.%x]",
510 icsid
[0],icsid
[1],icsid
[2],icsid
[3]);