1 /*
2    Copyright 2013-2017 Skytechnology sp. z o.o.
3 
4    This file is part of LizardFS.
5 
6    LizardFS is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, version 3.
9 
10    LizardFS is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with LizardFS. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "common/platform.h"
20 #include "common/chunk_connector.h"
21 
22 #include <errno.h>
23 #include <algorithm>
24 
25 #include "common/exceptions.h"
26 #include "common/mfserr.h"
27 #include "common/sockets.h"
28 #include "common/time_utils.h"
29 
timeoutTime(int64_t rtt,uint8_t tryCounter)30 static int64_t timeoutTime(int64_t rtt, uint8_t tryCounter) {
31 	return rtt * (1 << (tryCounter / 2)) * 3 / (tryCounter % 2 == 0 ? 3 : 2);
32 }
33 
ChunkConnector(uint32_t sourceIp)34 ChunkConnector::ChunkConnector(uint32_t sourceIp) : roundTripTime_ms_(20), sourceIp_(sourceIp) {
35 }
36 
startUsingConnection(const NetworkAddress & server,const Timeout & timeout) const37 int ChunkConnector::startUsingConnection(const NetworkAddress& server,
38 		const Timeout& timeout) const {
39 	int fd = -1;
40 	int retries = 0;
41 	int err = ETIMEDOUT;  // we want to return ETIMEDOUT on timeout.expired()
42 	while (!timeout.expired()) {
43 		fd = tcpsocket();
44 		if (fd < 0) {
45 			err = tcpgetlasterror();
46 			lzfs_pretty_syslog(LOG_WARNING, "can't create tcp socket: %s", strerr(err));
47 			break;
48 		}
49 		if (sourceIp_) {
50 			if (tcpnumbind(fd, sourceIp_, 0) < 0) {
51 				err = tcpgetlasterror();
52 				lzfs_pretty_syslog(LOG_WARNING, "can't bind to given ip: %s", strerr(err));
53 				tcpclose(fd);
54 				fd = -1;
55 				break;
56 			}
57 		}
58 		int64_t connectTimeout = std::min(
59 				timeoutTime(roundTripTime_ms_, retries),
60 				timeout.remaining_ms());
61 		connectTimeout = std::max(int64_t(1), connectTimeout); // tcpnumtoconnect doesn't like 0
62 		if (tcpnumtoconnect(fd, server.ip, server.port, connectTimeout) < 0) {
63 			err = tcpgetlasterror();
64 			tcpclose(fd);
65 			fd = -1;
66 		} else {
67 			break;
68 		}
69 		retries++;
70 	}
71 	if (fd < 0) {
72 		throw ChunkserverConnectionException(
73 				"Connection error: " + std::string(strerr(err)), server);
74 	}
75 	if (tcpnodelay(fd) < 0) {
76 		lzfs_pretty_syslog(LOG_WARNING, "can't set TCP_NODELAY: %s", strerr(tcpgetlasterror()));
77 	}
78 	return fd;
79 }
80 
endUsingConnection(int fd,const NetworkAddress &) const81 void ChunkConnector::endUsingConnection(int fd, const NetworkAddress& /* server */) const {
82 	tcpclose(fd);
83 }
84 
ChunkConnectorUsingPool(ConnectionPool & connectionPool,uint32_t sourceIp)85 ChunkConnectorUsingPool::ChunkConnectorUsingPool(ConnectionPool& connectionPool, uint32_t sourceIp)
86 		: ChunkConnector(sourceIp),
87 		  connectionPool_(connectionPool) {
88 }
89 
startUsingConnection(const NetworkAddress & server,const Timeout & timeout) const90 int ChunkConnectorUsingPool::startUsingConnection(const NetworkAddress& server,
91 		const Timeout& timeout) const {
92 	int fd = connectionPool_.getConnection(server);
93 	if (fd >= 0) {
94 		return fd;
95 	} else {
96 		return ChunkConnector::startUsingConnection(server, timeout);
97 	}
98 }
99 
endUsingConnection(int fd,const NetworkAddress & server) const100 void ChunkConnectorUsingPool::endUsingConnection(int fd, const NetworkAddress& server) const {
101 	connectionPool_.putConnection(fd, server, kConnectionPoolTimeout_s);
102 }
103