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