1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program 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 this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25
26 #include <platform.h>
27 #include <conn_cache.h>
28
29 #include <cfnet.h> /* AgentConnection */
30 #include <client_code.h> /* DisconnectServer */
31 #include <sequence.h> /* Seq */
32 #include <mutex.h> /* ThreadLock */
33 #include <communication.h> /* Hostname2IPString */
34 #include <misc_lib.h> /* CF_ASSERT */
35 #include <string_lib.h> /* StringEqual */
36
37
38 /**
39 Global cache for connections to servers, currently only used in cf-agent.
40
41 @note THREAD-SAFETY: yes this connection cache *is* thread-safe, but is
42 extremely slow if used intensely from multiple threads. It needs to
43 be redesigned from scratch for that, not a priority for
44 single-threaded cf-agent!
45 */
46
47
48 typedef struct
49 {
50 AgentConnection *conn;
51 enum ConnCacheStatus status; /* TODO unify with conn->conn_info->status */
52 } ConnCache_entry;
53
54
55 static pthread_mutex_t cft_conncache = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
56
57 static Seq *conn_cache = NULL;
58
59
ConnCache_Init()60 void ConnCache_Init()
61 {
62 ThreadLock(&cft_conncache);
63
64 assert(conn_cache == NULL);
65 conn_cache = SeqNew(100, free);
66
67 ThreadUnlock(&cft_conncache);
68 }
69
ConnCache_Destroy()70 void ConnCache_Destroy()
71 {
72 ThreadLock(&cft_conncache);
73
74 for (size_t i = 0; i < SeqLength(conn_cache); i++)
75 {
76 ConnCache_entry *svp = SeqAt(conn_cache, i);
77
78 CF_ASSERT(svp != NULL,
79 "Destroy: NULL ConnCache_entry!");
80 CF_ASSERT(svp->conn != NULL,
81 "Destroy: NULL connection in ConnCache_entry!");
82
83 DisconnectServer(svp->conn);
84 }
85
86 SeqDestroy(conn_cache);
87 conn_cache = NULL;
88
89 ThreadUnlock(&cft_conncache);
90 }
91
ConnCacheEntryMatchesConnection(ConnCache_entry * entry,const char * server,const char * port,ConnectionFlags flags)92 static bool ConnCacheEntryMatchesConnection(ConnCache_entry *entry,
93 const char *server,
94 const char *port,
95 ConnectionFlags flags)
96 {
97 return ConnectionFlagsEqual(&flags, &entry->conn->flags) &&
98 StringEqual(port, entry->conn->this_port) &&
99 StringEqual(server, entry->conn->this_server);
100 }
101
ConnCache_FindIdleMarkBusy(const char * server,const char * port,ConnectionFlags flags)102 AgentConnection *ConnCache_FindIdleMarkBusy(const char *server,
103 const char *port,
104 ConnectionFlags flags)
105 {
106 ThreadLock(&cft_conncache);
107
108 AgentConnection *ret_conn = NULL;
109 for (size_t i = 0; i < SeqLength(conn_cache); i++)
110 {
111 ConnCache_entry *svp = SeqAt(conn_cache, i);
112
113 CF_ASSERT(svp != NULL,
114 "FindIdle: NULL ConnCache_entry!");
115 CF_ASSERT(svp->conn != NULL,
116 "FindIdle: NULL connection in ConnCache_entry!");
117
118
119 if (svp->status == CONNCACHE_STATUS_BUSY)
120 {
121 Log(LOG_LEVEL_DEBUG,
122 "FindIdle: connection %p seems to be busy.",
123 svp->conn);
124 }
125 else if (svp->status == CONNCACHE_STATUS_OFFLINE)
126 {
127 Log(LOG_LEVEL_DEBUG,
128 "FindIdle: connection %p is marked as offline.",
129 svp->conn);
130 }
131 else if (svp->status == CONNCACHE_STATUS_BROKEN)
132 {
133 Log(LOG_LEVEL_DEBUG,
134 "FindIdle: connection %p is marked as broken.",
135 svp->conn);
136 }
137 else if (ConnCacheEntryMatchesConnection(svp, server, port, flags))
138 {
139 if (svp->conn->conn_info->sd >= 0)
140 {
141 assert(svp->status == CONNCACHE_STATUS_IDLE);
142
143 // Check connection state before returning it
144 int error = 0;
145 socklen_t len = sizeof(error);
146 if (getsockopt(svp->conn->conn_info->sd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
147 {
148 Log(LOG_LEVEL_DEBUG, "FindIdle: found connection to '%s' but could not get socket status, skipping.",
149 server);
150 svp->status = CONNCACHE_STATUS_BROKEN;
151 continue;
152 }
153 if (error != 0)
154 {
155 Log(LOG_LEVEL_DEBUG, "FindIdle: found connection to '%s' but connection is broken, skipping.",
156 server);
157 svp->status = CONNCACHE_STATUS_BROKEN;
158 continue;
159 }
160
161 Log(LOG_LEVEL_VERBOSE, "FindIdle:"
162 " found connection to '%s' already open and ready.",
163 server);
164
165 svp->status = CONNCACHE_STATUS_BUSY;
166 ret_conn = svp->conn;
167 break;
168 }
169 else
170 {
171 Log(LOG_LEVEL_VERBOSE, "FindIdle:"
172 " connection to '%s' has invalid socket descriptor %d!",
173 server, svp->conn->conn_info->sd);
174 svp->status = CONNCACHE_STATUS_BROKEN;
175 }
176 }
177 }
178
179 ThreadUnlock(&cft_conncache);
180
181 if (ret_conn == NULL)
182 {
183 Log(LOG_LEVEL_VERBOSE, "FindIdle:"
184 " no existing connection to '%s' is established.", server);
185 }
186
187 return ret_conn;
188 }
189
ConnCache_MarkNotBusy(AgentConnection * conn)190 void ConnCache_MarkNotBusy(AgentConnection *conn)
191 {
192 Log(LOG_LEVEL_DEBUG, "Searching for specific busy connection to: %s",
193 conn->this_server);
194
195 ThreadLock(&cft_conncache);
196
197 bool found = false;
198 for (size_t i = 0; i < SeqLength(conn_cache); i++)
199 {
200 ConnCache_entry *svp = SeqAt(conn_cache, i);
201
202 CF_ASSERT(svp != NULL,
203 "MarkNotBusy: NULL ConnCache_entry!");
204 CF_ASSERT(svp->conn != NULL,
205 "MarkNotBusy: NULL connection in ConnCache_entry!");
206
207 if (svp->conn == conn)
208 {
209 /* There might be many connections to the same server, some busy
210 * some not. But here we're searching by the address of the
211 * AgentConnection object. There can be only one. */
212 CF_ASSERT(svp->status == CONNCACHE_STATUS_BUSY,
213 "MarkNotBusy: status is not busy, it is %d!",
214 svp->status);
215
216 svp->status = CONNCACHE_STATUS_IDLE;
217 found = true;
218 break;
219 }
220 }
221
222 ThreadUnlock(&cft_conncache);
223
224 if (!found)
225 {
226 ProgrammingError("MarkNotBusy: No busy connection found!");
227 }
228
229 Log(LOG_LEVEL_DEBUG, "Busy connection just became free");
230 }
231
232 /* First time we open a connection, so store it. */
ConnCache_Add(AgentConnection * conn,enum ConnCacheStatus status)233 void ConnCache_Add(AgentConnection *conn, enum ConnCacheStatus status)
234 {
235 ConnCache_entry *svp = xmalloc(sizeof(*svp));
236 svp->status = status;
237 svp->conn = conn;
238
239 ThreadLock(&cft_conncache);
240 SeqAppend(conn_cache, svp);
241 ThreadUnlock(&cft_conncache);
242 }
243