1 /*
2  * storage_backend_iscsi.c: storage backend for iSCSI handling
3  *
4  * Copyright (C) 2007-2016 Red Hat, Inc.
5  * Copyright (C) 2007-2008 Daniel P. Berrange
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include <dirent.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 
29 #include "datatypes.h"
30 #include "driver.h"
31 #include "storage_backend_iscsi.h"
32 #include "viralloc.h"
33 #include "vircommand.h"
34 #include "virerror.h"
35 #include "virfile.h"
36 #include "viriscsi.h"
37 #include "viridentity.h"
38 #include "virlog.h"
39 #include "virobject.h"
40 #include "virstring.h"
41 #include "viruuid.h"
42 #include "virsecret.h"
43 #include "storage_util.h"
44 #include "virutil.h"
45 #include "virsecureerase.h"
46 
47 #define VIR_FROM_THIS VIR_FROM_STORAGE
48 
49 VIR_LOG_INIT("storage.storage_backend_iscsi");
50 
51 #define ISCSI_DEFAULT_TARGET_PORT 3260
52 
53 static char *
virStorageBackendISCSIPortal(virStoragePoolSource * source)54 virStorageBackendISCSIPortal(virStoragePoolSource *source)
55 {
56     char *portal = NULL;
57 
58     if (source->nhost != 1) {
59         virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
60                        _("Expected exactly 1 host for the storage pool"));
61         return NULL;
62     }
63 
64     if (source->hosts[0].port == 0)
65         source->hosts[0].port = ISCSI_DEFAULT_TARGET_PORT;
66 
67     if (strchr(source->hosts[0].name, ':')) {
68         portal = g_strdup_printf("[%s]:%d,1",
69                                  source->hosts[0].name,
70                                  source->hosts[0].port);
71     } else {
72         portal = g_strdup_printf("%s:%d,1",
73                                  source->hosts[0].name,
74                                  source->hosts[0].port);
75     }
76 
77     return portal;
78 }
79 
80 
81 static char *
virStorageBackendISCSISession(virStoragePoolObj * pool,bool probe)82 virStorageBackendISCSISession(virStoragePoolObj *pool,
83                               bool probe)
84 {
85     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
86     return virISCSIGetSession(def->source.devices[0].path, probe);
87 }
88 
89 
90 static int
virStorageBackendISCSIGetHostNumber(const char * sysfs_path,uint32_t * host)91 virStorageBackendISCSIGetHostNumber(const char *sysfs_path,
92                                     uint32_t *host)
93 {
94     g_autoptr(DIR) sysdir = NULL;
95     struct dirent *dirent = NULL;
96     int direrr;
97 
98     VIR_DEBUG("Finding host number from '%s'", sysfs_path);
99 
100     virWaitForDevices();
101 
102     if (virDirOpen(&sysdir, sysfs_path) < 0)
103         return -1;
104 
105     while ((direrr = virDirRead(sysdir, &dirent, sysfs_path)) > 0) {
106         if (STRPREFIX(dirent->d_name, "target")) {
107             if (sscanf(dirent->d_name, "target%u:", host) == 1) {
108                 return 0;
109             } else {
110                 virReportError(VIR_ERR_INTERNAL_ERROR,
111                                _("Failed to parse target '%s'"), dirent->d_name);
112                 return -1;
113             }
114         }
115     }
116 
117     if (direrr == 0) {
118         virReportError(VIR_ERR_INTERNAL_ERROR,
119                        _("Failed to get host number for iSCSI session "
120                          "with path '%s'"), sysfs_path);
121         return -1;
122     }
123 
124     return -1;
125 }
126 
127 static int
virStorageBackendISCSIFindLUs(virStoragePoolObj * pool,const char * session)128 virStorageBackendISCSIFindLUs(virStoragePoolObj *pool,
129                               const char *session)
130 {
131     uint32_t host;
132     g_autofree char *sysfs_path = NULL;
133 
134     sysfs_path = g_strdup_printf("/sys/class/iscsi_session/session%s/device",
135                                  session);
136 
137     if (virStorageBackendISCSIGetHostNumber(sysfs_path, &host) < 0)
138         return -1;
139 
140     if (virStorageBackendSCSIFindLUs(pool, host) < 0)
141         return -1;
142 
143     return 0;
144 }
145 
146 
147 static char *
virStorageBackendISCSIFindPoolSources(const char * srcSpec,unsigned int flags)148 virStorageBackendISCSIFindPoolSources(const char *srcSpec,
149                                       unsigned int flags)
150 {
151     size_t ntargets = 0;
152     char **targets = NULL;
153     char *ret = NULL;
154     size_t i;
155     virStoragePoolSourceList list = {
156         .type = VIR_STORAGE_POOL_ISCSI,
157         .nsources = 0,
158         .sources = NULL
159     };
160     g_autofree char *portal = NULL;
161     g_autoptr(virStoragePoolSource) source = NULL;
162 
163     virCheckFlags(0, NULL);
164 
165     if (!srcSpec) {
166         virReportError(VIR_ERR_INVALID_ARG, "%s",
167                        _("hostname must be specified for iscsi sources"));
168         return NULL;
169     }
170 
171     if (!(source = virStoragePoolDefParseSourceString(srcSpec,
172                                                       list.type)))
173         return NULL;
174 
175     if (source->nhost != 1) {
176         virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
177                        _("Expected exactly 1 host for the storage pool"));
178         goto cleanup;
179     }
180 
181     if (!(portal = virStorageBackendISCSIPortal(source)))
182         goto cleanup;
183 
184     if (virISCSIScanTargets(portal,
185                             source->initiator.iqn,
186                             false,
187                             &ntargets, &targets) < 0)
188         goto cleanup;
189 
190     list.sources = g_new0(virStoragePoolSource, ntargets);
191 
192     for (i = 0; i < ntargets; i++) {
193         list.sources[i].devices = g_new0(virStoragePoolSourceDevice, 1);
194         list.sources[i].hosts = g_new0(virStoragePoolSourceHost, 1);
195         list.sources[i].nhost = 1;
196         list.sources[i].hosts[0] = source->hosts[0];
197         list.sources[i].initiator = source->initiator;
198         list.sources[i].ndevice = 1;
199         list.sources[i].devices[0].path = targets[i];
200         list.nsources++;
201     }
202 
203     if (!(ret = virStoragePoolSourceListFormat(&list)))
204         goto cleanup;
205 
206  cleanup:
207     if (list.sources) {
208         for (i = 0; i < ntargets; i++) {
209             VIR_FREE(list.sources[i].hosts);
210             VIR_FREE(list.sources[i].devices);
211         }
212         VIR_FREE(list.sources);
213     }
214     for (i = 0; i < ntargets; i++)
215         VIR_FREE(targets[i]);
216     VIR_FREE(targets);
217     return ret;
218 }
219 
220 static int
virStorageBackendISCSICheckPool(virStoragePoolObj * pool,bool * isActive)221 virStorageBackendISCSICheckPool(virStoragePoolObj *pool,
222                                 bool *isActive)
223 {
224     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
225     g_autofree char *session = NULL;
226 
227     *isActive = false;
228 
229     if (def->source.nhost != 1) {
230         virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
231                        _("Expected exactly 1 host for the storage pool"));
232         return -1;
233     }
234 
235     if (def->source.hosts[0].name == NULL) {
236         virReportError(VIR_ERR_INTERNAL_ERROR,
237                        "%s", _("missing source host"));
238         return -1;
239     }
240 
241     if (def->source.ndevice != 1 ||
242         def->source.devices[0].path == NULL) {
243         virReportError(VIR_ERR_INTERNAL_ERROR,
244                        "%s", _("missing source device"));
245         return -1;
246     }
247 
248     if ((session = virStorageBackendISCSISession(pool, true)))
249         *isActive = true;
250     return 0;
251 }
252 
253 
254 static int
virStorageBackendISCSISetAuth(const char * portal,virStoragePoolSource * source)255 virStorageBackendISCSISetAuth(const char *portal,
256                               virStoragePoolSource *source)
257 {
258     g_autofree unsigned char *secret_value = NULL;
259     size_t secret_size;
260     g_autofree char *secret_str = NULL;
261     virStorageAuthDef *authdef = source->auth;
262     int ret = -1;
263     virConnectPtr conn = NULL;
264     VIR_IDENTITY_AUTORESTORE virIdentity *oldident = NULL;
265 
266     if (!authdef || authdef->authType == VIR_STORAGE_AUTH_TYPE_NONE)
267         return 0;
268 
269     VIR_DEBUG("username='%s' authType=%d seclookupdef.type=%d",
270               authdef->username, authdef->authType, authdef->seclookupdef.type);
271     if (authdef->authType != VIR_STORAGE_AUTH_TYPE_CHAP) {
272         virReportError(VIR_ERR_XML_ERROR, "%s",
273                        _("iscsi pool only supports 'chap' auth type"));
274         return -1;
275     }
276 
277     if (!(oldident = virIdentityElevateCurrent()))
278         return -1;
279 
280     conn = virGetConnectSecret();
281     if (!conn)
282         return -1;
283 
284     if (virSecretGetSecretString(conn, &authdef->seclookupdef,
285                                  VIR_SECRET_USAGE_TYPE_ISCSI,
286                                  &secret_value, &secret_size) < 0)
287         goto cleanup;
288 
289     secret_str = g_new0(char, secret_size + 1);
290     memcpy(secret_str, secret_value, secret_size);
291     virSecureErase(secret_value, secret_size);
292     secret_str[secret_size] = '\0';
293 
294     if (virISCSINodeUpdate(portal,
295                            source->devices[0].path,
296                            "node.session.auth.authmethod",
297                            "CHAP") < 0 ||
298         virISCSINodeUpdate(portal,
299                            source->devices[0].path,
300                            "node.session.auth.username",
301                            authdef->username) < 0 ||
302         virISCSINodeUpdate(portal,
303                            source->devices[0].path,
304                            "node.session.auth.password",
305                            secret_str) < 0)
306         goto cleanup;
307 
308     ret = 0;
309 
310  cleanup:
311     virSecureErase(secret_str, secret_size);
312     virObjectUnref(conn);
313     return ret;
314 }
315 
316 static int
virStorageBackendISCSIStartPool(virStoragePoolObj * pool)317 virStorageBackendISCSIStartPool(virStoragePoolObj *pool)
318 {
319     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
320     g_autofree char *portal = NULL;
321     g_autofree char *session = NULL;
322 
323     if (def->source.nhost != 1) {
324         virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
325                        _("Expected exactly 1 host for the storage pool"));
326         return -1;
327     }
328 
329     if (def->source.hosts[0].name == NULL) {
330         virReportError(VIR_ERR_INTERNAL_ERROR,
331                        "%s", _("missing source host"));
332         return -1;
333     }
334 
335     if (def->source.ndevice != 1 ||
336         def->source.devices[0].path == NULL) {
337         virReportError(VIR_ERR_INTERNAL_ERROR,
338                        "%s", _("missing source device"));
339         return -1;
340     }
341 
342     if ((session = virStorageBackendISCSISession(pool, true)) == NULL) {
343         if ((portal = virStorageBackendISCSIPortal(&def->source)) == NULL)
344             return -1;
345 
346         /* Create a static node record for the IQN target. Must be done
347          * in order for login to the target */
348         if (virISCSINodeNew(portal, def->source.devices[0].path) < 0)
349             return -1;
350 
351         if (virStorageBackendISCSISetAuth(portal, &def->source) < 0)
352             return -1;
353 
354         if (virISCSIConnectionLogin(portal,
355                                     def->source.initiator.iqn,
356                                     def->source.devices[0].path) < 0)
357             return -1;
358     }
359     return 0;
360 }
361 
362 static int
virStorageBackendISCSIRefreshPool(virStoragePoolObj * pool)363 virStorageBackendISCSIRefreshPool(virStoragePoolObj *pool)
364 {
365     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
366     g_autofree char *session = NULL;
367 
368     def->allocation = def->capacity = def->available = 0;
369 
370     if ((session = virStorageBackendISCSISession(pool, false)) == NULL)
371         return -1;
372     if (virISCSIRescanLUNs(session) < 0)
373         return -1;
374     if (virStorageBackendISCSIFindLUs(pool, session) < 0)
375         return -1;
376 
377     return 0;
378 }
379 
380 
381 static int
virStorageBackendISCSIStopPool(virStoragePoolObj * pool)382 virStorageBackendISCSIStopPool(virStoragePoolObj *pool)
383 {
384     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
385     g_autofree char *portal = NULL;
386     g_autofree char *session = NULL;
387 
388     if ((session = virStorageBackendISCSISession(pool, true)) == NULL)
389         return 0;
390 
391     if ((portal = virStorageBackendISCSIPortal(&def->source)) == NULL)
392         return -1;
393 
394     if (virISCSIConnectionLogout(portal,
395                                  def->source.initiator.iqn,
396                                  def->source.devices[0].path) < 0)
397         return -1;
398 
399     return 0;
400 }
401 
402 virStorageBackend virStorageBackendISCSI = {
403     .type = VIR_STORAGE_POOL_ISCSI,
404 
405     .checkPool = virStorageBackendISCSICheckPool,
406     .startPool = virStorageBackendISCSIStartPool,
407     .refreshPool = virStorageBackendISCSIRefreshPool,
408     .stopPool = virStorageBackendISCSIStopPool,
409     .findPoolSources = virStorageBackendISCSIFindPoolSources,
410     .uploadVol = virStorageBackendVolUploadLocal,
411     .downloadVol = virStorageBackendVolDownloadLocal,
412     .wipeVol = virStorageBackendVolWipeLocal,
413 };
414 
415 
416 int
virStorageBackendISCSIRegister(void)417 virStorageBackendISCSIRegister(void)
418 {
419     return virStorageBackendRegister(&virStorageBackendISCSI);
420 }
421