/
iSCSIUtils.c
382 lines (335 loc) · 16.1 KB
/
iSCSIUtils.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
/*
* Copyright (c) 2016, Nareg Sinenian
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "iSCSIUtils.h"
#include <ifaddrs.h>
/*! Minimum TCP port. */
static int PORT_MIN = 0;
/*! Maximum TCP port. */
static int PORT_MAX = (1 << sizeof(in_port_t)*8) -1;
/*! Verifies whether specified iSCSI qualified name (IQN) is valid per RFC3720.
* This function also validates 64-bit EUI names expressed as strings that
* start with the "eui" prefix.
* @param IQN the iSCSI qualified name.
* @return true if the name is valid, false otherwise. */
Boolean iSCSIUtilsValidateIQN(CFStringRef IQN)
{
// IEEE regular expression for matching IQN name
const char pattern[] = "^iqn[.][0-9]{4}-[0-9]{2}[.][[:alnum:]]{1,}[.]"
"[-A-Za-z0-9.]{1,255}"
"|^eui[.][[:xdigit:]]{16}$";
Boolean validName = false;
regex_t preg;
regcomp(&preg,pattern,REG_EXTENDED | REG_NOSUB);
CFIndex IQNLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(IQN),kCFStringEncodingASCII) + sizeof('\0');
char IQNBuffer[IQNLength];
CFStringGetCString(IQN,IQNBuffer,IQNLength,kCFStringEncodingASCII);
if(regexec(&preg,IQNBuffer,0,NULL,0) == 0)
validName = true;
regfree(&preg);
return validName;
}
/*! Validates the TCP port.
* @param port the TCP port to validate.
* @return true if the specified port is valid, false otherwise. */
Boolean iSCSIUtilsValidatePort(CFStringRef port)
{
SInt32 portNumber = CFStringGetIntValue(port);
return (portNumber >= PORT_MIN && portNumber <= PORT_MAX);
}
/*! Validates and parses an expression of the form <host>:<port> into its
* hostname (or IPv4/IPv6 address) and port. This function will return
* NULL if the specified expression is malformed, or an array containing
* either one or two elements (one if the portal is absent, two if it was
* specified.
* @param portal a string of the form <host>:<port>
* @return an array containing one or both portal parts, or NULL if the
* specified portal was malformed. */
CFArrayRef iSCSIUtilsCreateArrayByParsingPortalParts(CFStringRef portal)
{
// Regular expressions to match valid IPv4, IPv6 and DNS portal strings
const char IPv4Pattern[] = "^((((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|([0-9])?[0-9])[.]){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|([0-9])?[0-9]))(:([0-9]{1,5}))?)$";
const char IPv6Pattern[] = "^([[]?(([A-Fa-f0-9]{0,4}:){1,7}[A-Fa-f0-9]{0,4})([]]:([0-9]{1,5})?)?)$";
const char DNSPattern[] = "^((([A-Za-z0-9]{1,63}[.]){1,3}[A-Za-z0-9]{1,63})(:([0-9]{1,5}))?)$";
// Array of patterns to iterate, the indices of the matches that
// correspond to the hostname, port and the maximum number of matches
// Start with IPv4 as that is the most restrictive pattern, then work
// down to least restrictive (DNS names)
const char * patterns[] = {IPv4Pattern, IPv6Pattern, DNSPattern};
const int maxMatches[] = {10, 6, 6};
const int hostIndex[] = {2, 2, 2};
const int portIndex[] = {9, 5, 5};
int index = 0;
do
{
regex_t preg;
regcomp(&preg,patterns[index],REG_EXTENDED);
regmatch_t matches[maxMatches[index]];
memset(matches,0,sizeof(regmatch_t)*maxMatches[index]);
CFIndex portalLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(portal),kCFStringEncodingASCII) + sizeof('\0');
char portalBuffer[portalLength];
CFStringGetCString(portal,portalBuffer,portalLength,kCFStringEncodingASCII);
// Match against pattern[index]
if(regexec(&preg,portalBuffer,maxMatches[index],matches,0))
{
regfree(&preg);
index++;
continue;
}
CFMutableArrayRef portalParts = CFArrayCreateMutable(kCFAllocatorDefault,0,&kCFTypeArrayCallBacks);
// Get the host name
if(matches[hostIndex[index]].rm_so != -1)
{
CFRange rangeHost = CFRangeMake(matches[hostIndex[index]].rm_so,
matches[hostIndex[index]].rm_eo - matches[hostIndex[index]].rm_so);
CFStringRef host = CFStringCreateWithSubstring(kCFAllocatorDefault,
portal,rangeHost);
CFArrayAppendValue(portalParts,host);
CFRelease(host);
}
// Is the port available? If so, set it...
if(matches[portIndex[index]].rm_so != -1)
{
CFRange rangePort = CFRangeMake(matches[portIndex[index]].rm_so,
matches[portIndex[index]].rm_eo - matches[portIndex[index]].rm_so);
CFStringRef port = CFStringCreateWithSubstring(kCFAllocatorDefault,
portal,rangePort);
CFArrayAppendValue(portalParts,port);
CFRelease(port);
}
regfree(&preg);
return portalParts;
} while(index < sizeof(patterns)/sizeof(const char *));
return NULL;
}
/*! Gets the SCSI peripheral description from a peripheral device type code.
* @param peripheralDeviceType the single byte peripheral device descriptor
* as outlined in the SPC-4 r36d.
* @return a string describing the device (guaranteed to be a valid string). */
CFStringRef iSCSIUtilsGetSCSIPeripheralDeviceDescription(UInt8 peripheralDeviceType)
{
switch(peripheralDeviceType)
{
case kINQUIRY_PERIPHERAL_TYPE_DirectAccessSBCDevice:
return CFSTR("Block device");
case kINQUIRY_PERIPHERAL_TYPE_SequentialAccessSSCDevice:
return CFSTR("Sequential device");
case kINQUIRY_PERIPHERAL_TYPE_PrinterSSCDevice:
return CFSTR("Printer");
case kINQUIRY_PERIPHERAL_TYPE_ProcessorSPCDevice:
return CFSTR("Processor");
case kINQUIRY_PERIPHERAL_TYPE_WriteOnceSBCDevice:
return CFSTR("Write-once device");
case kINQUIRY_PERIPHERAL_TYPE_CDROM_MMCDevice:
return CFSTR("CD/DVD-ROM");
case kINQUIRY_PERIPHERAL_TYPE_ScannerSCSI2Device:
return CFSTR("Scanner");
case kINQUIRY_PERIPHERAL_TYPE_OpticalMemorySBCDevice:
return CFSTR("Optical memory device");
case kINQUIRY_PERIPHERAL_TYPE_MediumChangerSMCDevice:
return CFSTR("Medium changer");
case kINQUIRY_PERIPHERAL_TYPE_CommunicationsSSCDevice:
return CFSTR("Communications device");
/* 0x0A - 0x0B ASC IT8 Graphic Arts Prepress Devices */
case kINQUIRY_PERIPHERAL_TYPE_StorageArrayControllerSCC2Device:
return CFSTR("Storage array controller");
case kINQUIRY_PERIPHERAL_TYPE_EnclosureServicesSESDevice:
return CFSTR("Enclosure services device");
case kINQUIRY_PERIPHERAL_TYPE_SimplifiedDirectAccessRBCDevice:
return CFSTR("Simplified direct-access device");
case kINQUIRY_PERIPHERAL_TYPE_OpticalCardReaderOCRWDevice:
return CFSTR("Optical card reader/writer");
/* 0x10 - 0x1E Reserved Device Types */
case kINQUIRY_PERIPHERAL_TYPE_ObjectBasedStorageDevice:
return CFSTR("Object-based storage device");
case kINQUIRY_PERIPHERAL_TYPE_AutomationDriveInterface:
return CFSTR("Automation drive interface");
case kINQUIRY_PERIPHERAL_TYPE_WellKnownLogicalUnit:
return CFSTR("Well known logical unit");
case kINQUIRY_PERIPHERAL_TYPE_UnknownOrNoDeviceType:
default:
return CFSTR("Unknown or no device");
};
}
/*! Gets a string describing the iSCSI login status.
* @param statusCode the login status code.
* @return a string describing the login status (guaranteed to be a valid string). */
CFStringRef iSCSIUtilsGetStringForLoginStatus(enum iSCSILoginStatusCode statusCode)
{
switch(statusCode)
{
case kiSCSILoginSuccess:
return CFSTR("Login successful");
case kiSCSILoginAccessDenied:
return CFSTR("The target has denied access");
case kiSCSILoginAuthFail:
return CFSTR("Authentication failure");
case kiSCSILoginCantIncludeInSeession:
return CFSTR("Can't include the portal in the session");
case kiSCSILoginInitiatorError:
return CFSTR("An initiator error has occurred");
case kiSCSILoginInvalidReqDuringLogin:
return CFSTR("The initiator made an invalid request");
case kiSCSILoginMissingParam:
return CFSTR("Missing login parameters");
case kiSCSILoginNotFound:
return CFSTR("Target was not found");
case kiSCSILoginOutOfResources:
return CFSTR("Target is out of resources");
case kiSCSILoginServiceUnavailable:
return CFSTR("Target services unavailable");
case kiSCSILoginSessionDoesntExist:
return CFSTR("Session doesn't exist");
case kiSCSILoginSessionTypeUnsupported:
return CFSTR("Target doesn't support login");
case kiSCSILoginTargetHWorSWError:
return CFSTR("Target software or hardware error has occured");
case kiSCSILoginTargetMovedPerm:
return CFSTR("Target has permanently moved");
case kiSCSILoginTargetMovedTemp:
return CFSTR("Target has temporarily moved");
case kiSCSILoginTargetRemoved:
return CFSTR("Target has been removed");
case kiSCSILoginTooManyConnections:
return CFSTR("The session cannot support additional connections");
case kiSCSILoginUnsupportedVer:
return CFSTR("Target is incompatible with the initiator");
case kiSCSILoginInvalidStatusCode:
default:
return CFSTR("Unknown error occurred");
};
return CFSTR("");
}
/*! Gets a string describing the iSCSI logout status.
* @param statusCode the logout status code.
* @return a string describing the login status (guaranteed to be a valid string). */
CFStringRef iSCSIUtilsGetStringForLogoutStatus(enum iSCSILogoutStatusCode statusCode)
{
switch(statusCode)
{
case kiSCSILogoutSuccess:
return CFSTR("Logout successful");
case kiSCSILogoutCIDNotFound:
return CFSTR("The connection was not found");
case kiSCSILogoutCleanupFailed:
return CFSTR("Target cleanup of connection failed");
case kiSCSILogoutRecoveryNotSupported:
return CFSTR("Could not recover the connection");
case kiSCSILogoutInvalidStatusCode:
default:
return CFSTR("");
};
return CFSTR("");
}
/*! Creates address structures for an iSCSI target and the host (initiator)
* given an iSCSI portal reference. This function may be helpful when
* interfacing to low-level C networking APIs or other foundation libraries.
* @param portal an iSCSI portal.
* @param the target address structure (returned by this function).
* @param the host address structure (returned by this function). */
errno_t iSCSIUtilsGetAddressForPortal(iSCSIPortalRef portal,
struct sockaddr_storage * remoteAddress,
struct sockaddr_storage * localAddress)
{
if (!portal || !remoteAddress || !localAddress)
return EINVAL;
errno_t error = 0;
// Resolve the target node first and get a sockaddr info for it
CFStringRef targetAddr = iSCSIPortalGetAddress(portal);
CFIndex targetAddrLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(targetAddr),kCFStringEncodingASCII) + sizeof('\0');
char targetAddrBuffer[targetAddrLength];
CFStringGetCString(targetAddr,targetAddrBuffer,targetAddrLength,kCFStringEncodingASCII);
CFStringRef targetPort = iSCSIPortalGetPort(portal);
CFIndex targetPortLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(targetPort),kCFStringEncodingASCII) + sizeof('\0');
char targetPortBuffer[targetPortLength];
CFStringGetCString(targetPort,targetPortBuffer,targetPortLength,kCFStringEncodingASCII);
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
.ai_protocol = IPPROTO_TCP,
};
struct addrinfo * aiTarget = NULL;
if((error = getaddrinfo(targetAddrBuffer,targetPortBuffer,&hints,&aiTarget)))
return error;
// Copy the sock_addr structure into a sockaddr_storage structure (this
// may be either an IPv4 or IPv6 sockaddr structure)
memcpy(remoteAddress,aiTarget->ai_addr,aiTarget->ai_addrlen);
freeaddrinfo(aiTarget);
// If the default interface is to be used, prepare a structure for it
CFStringRef hostIface = iSCSIPortalGetHostInterface(portal);
if(CFStringCompare(hostIface,kiSCSIDefaultHostInterface,0) == kCFCompareEqualTo)
{
localAddress->ss_family = remoteAddress->ss_family;
// For completeness, setup the sockaddr_in structure
if(localAddress->ss_family == AF_INET)
{
struct sockaddr_in * sa = (struct sockaddr_in *)localAddress;
sa->sin_port = 0;
sa->sin_addr.s_addr = htonl(INADDR_ANY);
sa->sin_len = sizeof(struct sockaddr_in);
}
// TODO: test IPv6 functionality
else if(localAddress->ss_family == AF_INET6)
{
struct sockaddr_in6 * sa = (struct sockaddr_in6 *)localAddress;
sa->sin6_addr = in6addr_any;
}
return error;
}
// Otherwise we have to search the list of all interfaces for the specified
// interface and copy the corresponding address structure
struct ifaddrs * interfaceList;
if((error = getifaddrs(&interfaceList)))
return error;
error = EAFNOSUPPORT;
struct ifaddrs * interface = interfaceList;
while(interface)
{
// Check if interface supports the targets address family (e.g., IPv4)
if(interface->ifa_addr->sa_family == remoteAddress->ss_family)
{
CFStringRef currIface = CFStringCreateWithCStringNoCopy(
kCFAllocatorDefault,
interface->ifa_name,
kCFStringEncodingUTF8,
kCFAllocatorNull);
Boolean ifaceNameMatch =
CFStringCompare(currIface,hostIface,kCFCompareCaseInsensitive) == kCFCompareEqualTo;
CFRelease(currIface);
// Check if interface names match...
if(ifaceNameMatch)
{
memcpy(localAddress,interface->ifa_addr,interface->ifa_addr->sa_len);
error = 0;
break;
}
}
interface = interface->ifa_next;
}
freeifaddrs(interfaceList);
return error;
}