From cda35c03d60d090b1e8c6e85c379d5e4645cf8fd Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Sun, 14 Oct 2012 02:04:56 -0400 Subject: [PATCH] Sockets - Suppports IPv4 and IPv6 task server addresses. - Cleaned up error message when server is not available. --- ChangeLog.230 | 1 + src/Socket.cpp | 152 +++++++++++++++++++++------------------ src/Socket.h | 21 ++++-- src/commands/CmdSync.cpp | 43 ++++++----- 4 files changed, 123 insertions(+), 94 deletions(-) diff --git a/ChangeLog.230 b/ChangeLog.230 index f73a8e96b..6ac3736bc 100644 --- a/ChangeLog.230 +++ b/ChangeLog.230 @@ -10,6 +10,7 @@ Features + Adds a new 'synchronize' command to sync data with a task server. + Adds a new 'sync' verbosity token, which will reminds when a backlog builds up and needs a sync. + + Supports IPv4 and IPv6 server addresses. Bugs + Fixed bug so that 'limit:page' now considers footnote messages. diff --git a/src/Socket.cpp b/src/Socket.cpp index 395664b4a..9b26cbeae 100644 --- a/src/Socket.cpp +++ b/src/Socket.cpp @@ -29,45 +29,29 @@ #include #include #include +#include #include #include +#include #include +#include #include #include #include //////////////////////////////////////////////////////////////////////////////// -Socket::Socket (int family, int type, int protocol) : - _port (0), - _family (family), +Socket::Socket () : _socket (0), - _debug (false), - _limit (0) // Unlimited + _limit (0), // Unlimited + _debug (false) { - // family: AF_INET (IPv4), AF_INET6 (IPv6), AF_LOCAL, AF_ROUTE, AF_KEY. - // type: SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAW, SOCK_PACKET (Linux). - // protocol: IPPROTO_TCP, IPPROTO_UDP, IPPROTO_SCTP. - if ((_socket = ::socket (family, type, protocol)) < 0) - // This looks like a blank error message, but don't forget that the Error - // class will use ::strerror and append a system error message if errno is - // non-zero. - throw "ERROR: " + std::string (::strerror (errno)); - - // When a socket is closed, it remains unavailable for a while (netstat -an). - // Setting SO_REUSEADDR allows this program to assume control of a closed, but - // unavailable socket. - int on = 1; - if (::setsockopt (_socket, SOL_SOCKET, SO_REUSEADDR, (const void*)&on, sizeof (on)) < 0) - throw "ERROR: " + std::string (::strerror (errno)); } //////////////////////////////////////////////////////////////////////////////// Socket::Socket (int s) : - _port (0), - _family (AF_UNSPEC), _socket (s), - _debug (false), - _limit (0) // Unlimited + _limit (0), // Unlimited + _debug (false) { } @@ -79,49 +63,50 @@ Socket::~Socket () //////////////////////////////////////////////////////////////////////////////// // For clients. -void Socket::connect (const std::string& host, const int port) +void Socket::connect (const std::string& host, const std::string& port) { - _port = port; - std::string machine; + // use IPv4 or IPv6, does not matter. + struct addrinfo hints; + memset (&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // use my IP - // If the address is of the form \d+\.\d+\.\d+\.\d+ - bool ip = true; - for (unsigned int i = 0; i < host.length (); ++i) - if (!isdigit (host[i]) && host[i] != '.') + struct addrinfo* res; + if (::getaddrinfo (host.c_str (), port.c_str (), &hints, &res) != 0) + throw "ERROR: " + std::string (::gai_strerror (errno)); + + // Try them all, stop on success. + struct addrinfo* p; + for (p = res; p != NULL; p = p->ai_next) + { + if ((_socket = ::socket (p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) + continue; + + // When a socket is closed, it remains unavailable for a while (netstat -an). + // Setting SO_REUSEADDR allows this program to assume control of a closed, + // but unavailable socket. + int on = 1; + if (::setsockopt (_socket, + SOL_SOCKET, + SO_REUSEADDR, + (const void*) &on, + sizeof (on)) == -1) + throw "ERROR: " + std::string (::strerror (errno)); + + if (::connect (_socket, p->ai_addr, p->ai_addrlen) == -1) { - ip = false; - break; + close (); + continue; } - if (ip) - { - struct in_addr in; - in.s_addr = inet_addr (host.c_str ()); - struct hostent* he = gethostbyaddr ((char*) &in, - sizeof (in.s_addr), - AF_INET); - if (!he) - throw std::string ("ERROR: Cannot resolve host from ") + host; - - machine = he->h_name; - } - else - { - struct hostent* he = gethostbyname (host.c_str ()); - if (!he) - throw std::string ("ERROR: Cannot resolve host from ") + host; - - machine = inet_ntoa (*((struct in_addr*)he->h_addr)); + break; } - struct sockaddr_in server = {0}; - server.sin_family = _family; - server.sin_port = htons (_port); - if (::inet_pton (_family, machine.c_str (), &server.sin_addr) != 1) - throw "ERROR: " + std::string (::strerror (errno)); + free (res); - if (::connect (_socket, (struct sockaddr*) &server, sizeof (server)) < 0) - throw "ERROR: " + std::string (::strerror (errno)); + if (p == NULL) + throw "ERROR: Could not connect to " + host + " " + port; } //////////////////////////////////////////////////////////////////////////////// @@ -134,15 +119,36 @@ void Socket::close () //////////////////////////////////////////////////////////////////////////////// // For servers. -void Socket::bind (int family, int port) +void Socket::bind (const std::string& port) { - struct sockaddr_in server; - memset (&server, 0, sizeof (server)); - server.sin_family = _family = family; - server.sin_addr.s_addr = htonl (INADDR_ANY); - server.sin_port = htons (_port = port); + // use IPv4 or IPv6, does not matter. + struct addrinfo hints; + memset (&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // use my IP - if (::bind (_socket, (struct sockaddr*) &server, sizeof (server)) < 0) + struct addrinfo* res; + if (::getaddrinfo (NULL, port.c_str (), &hints, &res) != 0) + throw "ERROR: " + std::string (::gai_strerror (errno)); + + if ((_socket = ::socket (res->ai_family, + res->ai_socktype, + res->ai_protocol)) == -1) + throw "ERROR: Can not bind to port " + port; + + // When a socket is closed, it remains unavailable for a while (netstat -an). + // Setting SO_REUSEADDR allows this program to assume control of a closed, but + // unavailable socket. + int on = 1; + if (::setsockopt (_socket, + SOL_SOCKET, + SO_REUSEADDR, + (const void*) &on, + sizeof (on)) == -1) + throw "ERROR: " + std::string (::strerror (errno)); + + if (::bind (_socket, res->ai_addr, res->ai_addrlen) == -1) throw "ERROR: " + std::string (::strerror (errno)); } @@ -156,8 +162,8 @@ void Socket::listen (int queue /*= 5*/) //////////////////////////////////////////////////////////////////////////////// int Socket::accept () { - struct sockaddr_in client; - socklen_t length = sizeof (client); + struct sockaddr_storage client; + socklen_t length = sizeof client; int connection; do @@ -288,4 +294,14 @@ void Socket::debug () } //////////////////////////////////////////////////////////////////////////////// +// get sockaddr, IPv4 or IPv6: +void* Socket::get_in_addr (struct sockaddr* sa) +{ + if (sa->sa_family == AF_INET) + return &(((struct sockaddr_in*) sa)->sin_addr); + + return &(((struct sockaddr_in6*) sa)->sin6_addr); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Socket.h b/src/Socket.h index 270582784..29f6ce402 100644 --- a/src/Socket.h +++ b/src/Socket.h @@ -38,25 +38,32 @@ class Socket { public: - Socket (int, int, int); + Socket (); Socket (int); ~Socket (); - void connect (const std::string&, const int); - void close (); - void bind (int, int); + + // Client + void connect (const std::string&, const std::string&); + + // Server + void bind (const std::string&); void listen (int queue = 5); int accept (); void read (std::string&); void write (const std::string&); + + void close (); + void limit (int); void debug (); private: - int _port; - int _family; + void* get_in_addr (struct sockaddr*); + +private: int _socket; - bool _debug; int _limit; + bool _debug; }; #endif diff --git a/src/commands/CmdSync.cpp b/src/commands/CmdSync.cpp index 90a8bcffb..fad7de479 100644 --- a/src/commands/CmdSync.cpp +++ b/src/commands/CmdSync.cpp @@ -28,6 +28,7 @@ #define L10N // Localization complete. #include +#include #include #include #include @@ -54,10 +55,12 @@ int CmdSync::execute (std::string& output) int status = 0; context.timer_sync.start (); + std::stringstream out; + // If no server is set up, quit. std::string connection = context.config.get ("taskd.server"); if (connection == "" || - connection.find (':') == std::string::npos) + connection.rfind (':') == std::string::npos) throw std::string (STRING_CMD_SYNC_NO_SERVER); // Obtain credentials. @@ -99,8 +102,8 @@ int CmdSync::execute (std::string& output) request.setPayload (payload); - std::cout << format (STRING_CMD_SYNC_PROGRESS, connection) - << "\n"; + out << format (STRING_CMD_SYNC_PROGRESS, connection) + << "\n"; Msg response; if (send (connection, request, response)) @@ -135,22 +138,22 @@ int CmdSync::execute (std::string& output) Task dummy; if (context.tdb2.get (uuid, dummy)) { - std::cout << " " - << colorChanged.colorize ( - format (STRING_CMD_SYNC_MOD, - uuid, - from_server.get ("description"))) - << "\n"; + out << " " + << colorChanged.colorize ( + format (STRING_CMD_SYNC_MOD, + uuid, + from_server.get ("description"))) + << "\n"; context.tdb2.modify (from_server, false); } else { - std::cout << " " - << colorAdded.colorize ( - format (STRING_CMD_SYNC_ADD, - uuid, - from_server.get ("description"))) - << "'\n"; + out << " " + << colorAdded.colorize ( + format (STRING_CMD_SYNC_ADD, + uuid, + from_server.get ("description"))) + << "\n"; context.tdb2.add (from_server, false); } } @@ -228,6 +231,8 @@ int CmdSync::execute (std::string& output) status = 1; } + out << "\n"; + output = out.str (); context.timer_sync.stop (); return status; } @@ -238,16 +243,16 @@ bool CmdSync::send ( const Msg& request, Msg& response) { - std::string::size_type colon = to.find (':'); + std::string::size_type colon = to.rfind (':'); if (colon == std::string::npos) throw format (STRING_CMD_SYNC_BAD_SERVER, to); std::string server = to.substr (0, colon); - int port = strtoimax (to.substr (colon + 1).c_str (), NULL, 10); + std::string port = to.substr (colon + 1); try { - Socket s (AF_INET, SOCK_STREAM, IPPROTO_TCP); + Socket s; s.connect (server, port); s.write (request.serialize () + "\n"); @@ -261,7 +266,7 @@ bool CmdSync::send ( catch (std::string& error) { - context.error (error); + context.debug (error); } // Indicate message failed.