1 /** @file
2   iSCSI DHCP6 related configuration routines.
3 
4 Copyright (c) 2009 - 2015, Intel Corporation. All rights reserved.<BR>
5 This program and the accompanying materials
6 are licensed and made available under the terms and conditions of the BSD License
7 which accompanies this distribution.  The full text of the license may be found at
8 http://opensource.org/licenses/bsd-license.php
9 
10 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12 
13 **/
14 
15 #include "IScsiImpl.h"
16 
17 
18 /**
19   Extract the Root Path option and get the required target information from
20   Boot File Uniform Resource Locator (URL) Option.
21 
22   @param[in]       RootPath      The RootPath string.
23   @param[in]       Length        Length of the RootPath option payload.
24   @param[in, out]  ConfigData    The iSCSI session configuration data read from
25                                  nonvolatile device.
26 
27   @retval EFI_SUCCESS            All required information is extracted from the
28                                  RootPath option.
29   @retval EFI_NOT_FOUND          The RootPath is not an iSCSI RootPath.
30   @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory.
31   @retval EFI_INVALID_PARAMETER  The RootPath is malformatted.
32 
33 **/
34 EFI_STATUS
IScsiDhcp6ExtractRootPath(IN CHAR8 * RootPath,IN UINT16 Length,IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA * ConfigData)35 IScsiDhcp6ExtractRootPath (
36   IN     CHAR8                        *RootPath,
37   IN     UINT16                       Length,
38   IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData
39   )
40 {
41   EFI_STATUS                  Status;
42   UINT16                      IScsiRootPathIdLen;
43   CHAR8                       *TmpStr;
44   ISCSI_ROOT_PATH_FIELD       Fields[RP_FIELD_IDX_MAX];
45   ISCSI_ROOT_PATH_FIELD       *Field;
46   UINT32                      FieldIndex;
47   UINT8                       Index;
48   ISCSI_SESSION_CONFIG_NVDATA *ConfigNvData;
49   EFI_IP_ADDRESS              Ip;
50   UINT8                       IpMode;
51 
52   ConfigNvData = &ConfigData->SessionConfigData;
53 
54   //
55   // "iscsi:"<servername>":"<protocol>":"<port>":"<LUN>":"<targetname>
56   //
57   IScsiRootPathIdLen = (UINT16) AsciiStrLen (ISCSI_ROOT_PATH_ID);
58 
59   if ((Length <= IScsiRootPathIdLen) ||
60       (CompareMem (RootPath, ISCSI_ROOT_PATH_ID, IScsiRootPathIdLen) != 0)) {
61     return EFI_NOT_FOUND;
62   }
63   //
64   // Skip the iSCSI RootPath ID "iscsi:".
65   //
66   RootPath = RootPath + IScsiRootPathIdLen;
67   Length   = (UINT16) (Length - IScsiRootPathIdLen);
68 
69   TmpStr   = (CHAR8 *) AllocatePool (Length + 1);
70   if (TmpStr == NULL) {
71     return EFI_OUT_OF_RESOURCES;
72   }
73 
74   CopyMem (TmpStr, RootPath, Length);
75   TmpStr[Length]  = '\0';
76 
77   Index           = 0;
78   FieldIndex      = 0;
79   ZeroMem (&Fields[0], sizeof (Fields));
80 
81   //
82   // Extract SERVERNAME field in the Root Path option.
83   //
84   if (TmpStr[Index] != ISCSI_ROOT_PATH_ADDR_START_DELIMITER) {
85     Status = EFI_INVALID_PARAMETER;
86     goto ON_EXIT;
87   } else {
88     Index++;
89   }
90 
91   Fields[RP_FIELD_IDX_SERVERNAME].Str = &TmpStr[Index];
92 
93   while ((TmpStr[Index] != ISCSI_ROOT_PATH_ADDR_END_DELIMITER) && (Index < Length)) {
94     Index++;
95   }
96 
97   //
98   // Skip ']' and ':'.
99   //
100   TmpStr[Index] = '\0';
101   Index += 2;
102 
103   Fields[RP_FIELD_IDX_SERVERNAME].Len = (UINT8) AsciiStrLen (Fields[RP_FIELD_IDX_SERVERNAME].Str);
104 
105   //
106   // Extract others fields in the Root Path option string.
107   //
108   for (FieldIndex = 1; (FieldIndex < RP_FIELD_IDX_MAX) && (Index < Length); FieldIndex++) {
109 
110     if (TmpStr[Index] != ISCSI_ROOT_PATH_FIELD_DELIMITER) {
111       Fields[FieldIndex].Str = &TmpStr[Index];
112     }
113 
114     while ((TmpStr[Index] != ISCSI_ROOT_PATH_FIELD_DELIMITER) && (Index < Length)) {
115       Index++;
116     }
117 
118     if (TmpStr[Index] == ISCSI_ROOT_PATH_FIELD_DELIMITER) {
119       if (FieldIndex != RP_FIELD_IDX_TARGETNAME) {
120         TmpStr[Index] = '\0';
121         Index++;
122       }
123 
124       if (Fields[FieldIndex].Str != NULL) {
125         Fields[FieldIndex].Len = (UINT8) AsciiStrLen (Fields[FieldIndex].Str);
126       }
127     }
128   }
129 
130   if (FieldIndex != RP_FIELD_IDX_MAX) {
131     Status = EFI_INVALID_PARAMETER;
132     goto ON_EXIT;
133   }
134 
135   if ((Fields[RP_FIELD_IDX_SERVERNAME].Str == NULL) ||
136       (Fields[RP_FIELD_IDX_TARGETNAME].Str == NULL) ||
137       (Fields[RP_FIELD_IDX_PROTOCOL].Len > 1)
138       ) {
139 
140     Status = EFI_INVALID_PARAMETER;
141     goto ON_EXIT;
142   }
143   //
144   // Get the IP address of the target.
145   //
146   Field   = &Fields[RP_FIELD_IDX_SERVERNAME];
147   if (ConfigNvData->IpMode < IP_MODE_AUTOCONFIG) {
148     IpMode = ConfigNvData->IpMode;
149   } else {
150     IpMode = ConfigData->AutoConfigureMode;
151   }
152 
153   Status = IScsiAsciiStrToIp (Field->Str, IpMode, &Ip);
154   CopyMem (&ConfigNvData->TargetIp, &Ip, sizeof (EFI_IP_ADDRESS));
155 
156 
157   if (EFI_ERROR (Status)) {
158     goto ON_EXIT;
159   }
160   //
161   // Check the protocol type.
162   //
163   Field = &Fields[RP_FIELD_IDX_PROTOCOL];
164   if ((Field->Str != NULL) && ((*(Field->Str) - '0') != EFI_IP_PROTO_TCP)) {
165     Status = EFI_INVALID_PARAMETER;
166     goto ON_EXIT;
167   }
168   //
169   // Get the port of the iSCSI target.
170   //
171   Field = &Fields[RP_FIELD_IDX_PORT];
172   if (Field->Str != NULL) {
173     ConfigNvData->TargetPort = (UINT16) AsciiStrDecimalToUintn (Field->Str);
174   } else {
175     ConfigNvData->TargetPort = ISCSI_WELL_KNOWN_PORT;
176   }
177   //
178   // Get the LUN.
179   //
180   Field = &Fields[RP_FIELD_IDX_LUN];
181   if (Field->Str != NULL) {
182     Status = IScsiAsciiStrToLun (Field->Str, ConfigNvData->BootLun);
183     if (EFI_ERROR (Status)) {
184       goto ON_EXIT;
185     }
186   } else {
187     ZeroMem (ConfigNvData->BootLun, sizeof (ConfigNvData->BootLun));
188   }
189   //
190   // Get the target iSCSI Name.
191   //
192   Field = &Fields[RP_FIELD_IDX_TARGETNAME];
193 
194   if (AsciiStrLen (Field->Str) > ISCSI_NAME_MAX_SIZE - 1) {
195     Status = EFI_INVALID_PARAMETER;
196     goto ON_EXIT;
197   }
198   //
199   // Validate the iSCSI name.
200   //
201   Status = IScsiNormalizeName (Field->Str, AsciiStrLen (Field->Str));
202   if (EFI_ERROR (Status)) {
203     goto ON_EXIT;
204   }
205 
206   AsciiStrCpyS (ConfigNvData->TargetName, ISCSI_NAME_MAX_SIZE, Field->Str);
207 
208 ON_EXIT:
209 
210   FreePool (TmpStr);
211 
212   return Status;
213 }
214 
215 /**
216   EFI_DHCP6_INFO_CALLBACK is provided by the consumer of the EFI DHCPv6 Protocol
217   instance to intercept events that occurs in the DHCPv6 Information Request
218   exchange process.
219 
220   @param[in]  This              Pointer to the EFI_DHCP6_PROTOCOL instance that
221                                 is used to configure this  callback function.
222   @param[in]  Context           Pointer to the context that is initialized in
223                                 the EFI_DHCP6_PROTOCOL.InfoRequest().
224   @param[in]  Packet            Pointer to Reply packet that has been received.
225                                 The EFI DHCPv6 Protocol instance is responsible
226                                 for freeing the buffer.
227 
228   @retval EFI_SUCCESS           Tell the EFI DHCPv6 Protocol instance to finish
229                                 Information Request exchange process.
230   @retval EFI_NOT_READY         Tell the EFI DHCPv6 Protocol instance to continue
231                                 Information Request exchange process.
232   @retval EFI_ABORTED           Tell the EFI DHCPv6 Protocol instance to abort
233                                 the Information Request exchange process.
234   @retval EFI_UNSUPPORTED       Tell the EFI DHCPv6 Protocol instance to finish
235                                 the Information Request exchange process because some
236                                 request information are not received.
237 
238 **/
239 EFI_STATUS
240 EFIAPI
IScsiDhcp6ParseReply(IN EFI_DHCP6_PROTOCOL * This,IN VOID * Context,IN EFI_DHCP6_PACKET * Packet)241 IScsiDhcp6ParseReply (
242   IN EFI_DHCP6_PROTOCOL          *This,
243   IN VOID                        *Context,
244   IN EFI_DHCP6_PACKET            *Packet
245   )
246 {
247   EFI_STATUS                  Status;
248   UINT32                      Index;
249   UINT32                      OptionCount;
250   EFI_DHCP6_PACKET_OPTION     *BootFileOpt;
251   EFI_DHCP6_PACKET_OPTION     **OptionList;
252   ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData;
253   UINT16                      ParaLen;
254 
255   OptionCount = 0;
256   BootFileOpt = NULL;
257 
258   Status      = This->Parse (This, Packet, &OptionCount, NULL);
259   if (Status != EFI_BUFFER_TOO_SMALL) {
260     return EFI_NOT_READY;
261   }
262 
263   OptionList = AllocateZeroPool (OptionCount * sizeof (EFI_DHCP6_PACKET_OPTION *));
264   if (OptionList == NULL) {
265     return EFI_NOT_READY;
266   }
267 
268   Status = This->Parse (This, Packet, &OptionCount, OptionList);
269   if (EFI_ERROR (Status)) {
270     Status = EFI_NOT_READY;
271     goto Exit;
272   }
273 
274   ConfigData = (ISCSI_ATTEMPT_CONFIG_NVDATA *) Context;
275 
276   for (Index = 0; Index < OptionCount; Index++) {
277     OptionList[Index]->OpCode = NTOHS (OptionList[Index]->OpCode);
278     OptionList[Index]->OpLen  = NTOHS (OptionList[Index]->OpLen);
279 
280     //
281     // Get DNS server addresses from this reply packet.
282     //
283     if (OptionList[Index]->OpCode == DHCP6_OPT_DNS_SERVERS) {
284 
285       if (((OptionList[Index]->OpLen & 0xf) != 0) || (OptionList[Index]->OpLen == 0)) {
286         Status = EFI_UNSUPPORTED;
287         goto Exit;
288       }
289       //
290       // Primary DNS server address.
291       //
292       CopyMem (&ConfigData->PrimaryDns, &OptionList[Index]->Data[0], sizeof (EFI_IPv6_ADDRESS));
293 
294       if (OptionList[Index]->OpLen > 16) {
295         //
296         // Secondary DNS server address
297         //
298         CopyMem (&ConfigData->SecondaryDns, &OptionList[Index]->Data[16], sizeof (EFI_IPv6_ADDRESS));
299       }
300 
301     } else if (OptionList[Index]->OpCode == DHCP6_OPT_BOOT_FILE_URL) {
302       //
303       // The server sends this option to inform the client about an URL to a boot file.
304       //
305       BootFileOpt = OptionList[Index];
306     } else if (OptionList[Index]->OpCode == DHCP6_OPT_BOOT_FILE_PARA) {
307       //
308       // The server sends this option to inform the client about DHCP6 server address.
309       //
310       if (OptionList[Index]->OpLen < 18) {
311         Status = EFI_UNSUPPORTED;
312         goto Exit;
313       }
314       //
315       // Check param-len 1, should be 16 bytes.
316       //
317       CopyMem (&ParaLen, &OptionList[Index]->Data[0], sizeof (UINT16));
318       if (NTOHS (ParaLen) != 16) {
319         Status = EFI_UNSUPPORTED;
320         goto Exit;
321       }
322 
323       CopyMem (&ConfigData->DhcpServer, &OptionList[Index]->Data[2], sizeof (EFI_IPv6_ADDRESS));
324     }
325   }
326 
327   if (BootFileOpt == NULL) {
328     Status = EFI_UNSUPPORTED;
329     goto Exit;
330   }
331 
332   //
333   // Get iSCSI root path from Boot File Uniform Resource Locator (URL) Option
334   //
335   Status = IScsiDhcp6ExtractRootPath (
336              (CHAR8 *) BootFileOpt->Data,
337              BootFileOpt->OpLen,
338              ConfigData
339              );
340 
341 Exit:
342 
343   FreePool (OptionList);
344   return Status;
345 }
346 
347 
348 /**
349   Parse the DHCP ACK to get the address configuration and DNS information.
350 
351   @param[in]       Image         The handle of the driver image.
352   @param[in]       Controller    The handle of the controller;
353   @param[in, out]  ConfigData    The attempt configuration data.
354 
355   @retval EFI_SUCCESS            The DNS information is got from the DHCP ACK.
356   @retval EFI_NO_MAPPING         DHCP failed to acquire address and other
357                                  information.
358   @retval EFI_INVALID_PARAMETER  The DHCP ACK's DNS option is malformatted.
359   @retval EFI_DEVICE_ERROR       Some unexpected error occurred.
360   @retval EFI_OUT_OF_RESOURCES   There is no sufficient resource to finish the
361                                  operation.
362   @retval EFI_NO_MEDIA           There was a media error.
363 
364 **/
365 EFI_STATUS
IScsiDoDhcp6(IN EFI_HANDLE Image,IN EFI_HANDLE Controller,IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA * ConfigData)366 IScsiDoDhcp6 (
367   IN     EFI_HANDLE                  Image,
368   IN     EFI_HANDLE                  Controller,
369   IN OUT ISCSI_ATTEMPT_CONFIG_NVDATA *ConfigData
370   )
371 {
372   EFI_HANDLE                Dhcp6Handle;
373   EFI_DHCP6_PROTOCOL        *Dhcp6;
374   EFI_STATUS                Status;
375   EFI_STATUS                TimerStatus;
376   EFI_DHCP6_PACKET_OPTION   *Oro;
377   EFI_DHCP6_RETRANSMISSION  InfoReqReXmit;
378   EFI_EVENT                 Timer;
379   BOOLEAN                   MediaPresent;
380 
381   //
382   // Check media status before doing DHCP.
383   //
384   MediaPresent = TRUE;
385   NetLibDetectMedia (Controller, &MediaPresent);
386   if (!MediaPresent) {
387     return EFI_NO_MEDIA;
388   }
389 
390   //
391   // iSCSI will only request target info from DHCPv6 server.
392   //
393   if (!ConfigData->SessionConfigData.TargetInfoFromDhcp) {
394     return EFI_SUCCESS;
395   }
396 
397   Dhcp6Handle = NULL;
398   Dhcp6       = NULL;
399   Oro         = NULL;
400   Timer       = NULL;
401 
402   //
403   // Create a DHCP6 child instance and get the protocol.
404   //
405   Status = NetLibCreateServiceChild (
406              Controller,
407              Image,
408              &gEfiDhcp6ServiceBindingProtocolGuid,
409              &Dhcp6Handle
410              );
411   if (EFI_ERROR (Status)) {
412     return Status;
413   }
414 
415   Status = gBS->OpenProtocol (
416                   Dhcp6Handle,
417                   &gEfiDhcp6ProtocolGuid,
418                   (VOID **) &Dhcp6,
419                   Image,
420                   Controller,
421                   EFI_OPEN_PROTOCOL_BY_DRIVER
422                   );
423   if (EFI_ERROR (Status)) {
424     goto ON_EXIT;
425   }
426 
427   Oro = AllocateZeroPool (sizeof (EFI_DHCP6_PACKET_OPTION) + 5);
428   if (Oro == NULL) {
429     Status = EFI_OUT_OF_RESOURCES;
430     goto ON_EXIT;
431   }
432 
433   //
434   // Ask the server to reply with DNS and Boot File URL options by info request.
435   // All members in EFI_DHCP6_PACKET_OPTION are in network order.
436   //
437   Oro->OpCode  = HTONS (DHCP6_OPT_REQUEST_OPTION);
438   Oro->OpLen   = HTONS (2 * 3);
439   Oro->Data[1] = DHCP6_OPT_DNS_SERVERS;
440   Oro->Data[3] = DHCP6_OPT_BOOT_FILE_URL;
441   Oro->Data[5] = DHCP6_OPT_BOOT_FILE_PARA;
442 
443   InfoReqReXmit.Irt = 4;
444   InfoReqReXmit.Mrc = 1;
445   InfoReqReXmit.Mrt = 10;
446   InfoReqReXmit.Mrd = 30;
447 
448   Status = Dhcp6->InfoRequest (
449                     Dhcp6,
450                     TRUE,
451                     Oro,
452                     0,
453                     NULL,
454                     &InfoReqReXmit,
455                     NULL,
456                     IScsiDhcp6ParseReply,
457                     ConfigData
458                     );
459   if (Status == EFI_NO_MAPPING) {
460     Status = gBS->CreateEvent (EVT_TIMER, TPL_CALLBACK, NULL, NULL, &Timer);
461     if (EFI_ERROR (Status)) {
462       goto ON_EXIT;
463     }
464 
465     Status = gBS->SetTimer (
466                     Timer,
467                     TimerRelative,
468                     ISCSI_GET_MAPPING_TIMEOUT
469                     );
470 
471     if (EFI_ERROR (Status)) {
472       goto ON_EXIT;
473     }
474 
475     do {
476 
477       TimerStatus = gBS->CheckEvent (Timer);
478 
479       if (!EFI_ERROR (TimerStatus)) {
480         Status = Dhcp6->InfoRequest (
481                           Dhcp6,
482                           TRUE,
483                           Oro,
484                           0,
485                           NULL,
486                           &InfoReqReXmit,
487                           NULL,
488                           IScsiDhcp6ParseReply,
489                           ConfigData
490                           );
491       }
492 
493     } while (TimerStatus == EFI_NOT_READY);
494 
495   }
496 
497 ON_EXIT:
498 
499   if (Oro != NULL) {
500     FreePool (Oro);
501   }
502 
503   if (Timer != NULL) {
504     gBS->CloseEvent (Timer);
505   }
506 
507   if (Dhcp6 != NULL) {
508     gBS->CloseProtocol (
509            Dhcp6Handle,
510            &gEfiDhcp6ProtocolGuid,
511            Image,
512            Controller
513            );
514   }
515 
516   NetLibDestroyServiceChild (
517     Controller,
518     Image,
519     &gEfiDhcp6ServiceBindingProtocolGuid,
520     Dhcp6Handle
521     );
522 
523   return Status;
524 }
525 
526