1 /** @file
2   iSCSI DHCP related configuration routines.
3 
4 Copyright (c) 2004 - 2016, 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   Extract the Root Path option and get the required target information.
19 
20   @param[in]        RootPath         The RootPath.
21   @param[in]        Length           Length of the RootPath option payload.
22   @param[in, out]   ConfigNvData     The iSCSI session configuration data read from nonvolatile device.
23 
24   @retval EFI_SUCCESS           All required information is extracted from the RootPath option.
25   @retval EFI_NOT_FOUND         The RootPath is not an iSCSI RootPath.
26   @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory.
27   @retval EFI_INVALID_PARAMETER The RootPath is mal-formatted.
28 **/
29 EFI_STATUS
IScsiDhcpExtractRootPath(IN CHAR8 * RootPath,IN UINT8 Length,IN OUT ISCSI_SESSION_CONFIG_NVDATA * ConfigNvData)30 IScsiDhcpExtractRootPath (
31   IN      CHAR8                        *RootPath,
32   IN      UINT8                        Length,
33   IN OUT  ISCSI_SESSION_CONFIG_NVDATA  *ConfigNvData
34   )
35 {
36   EFI_STATUS            Status;
37   UINT8                 IScsiRootPathIdLen;
38   CHAR8                 *TmpStr;
39   ISCSI_ROOT_PATH_FIELD Fields[RP_FIELD_IDX_MAX];
40   ISCSI_ROOT_PATH_FIELD *Field;
41   UINT32                FieldIndex;
42   UINT8                 Index;
43 
44   //
45   // "iscsi:"<servername>":"<protocol>":"<port>":"<LUN>":"<targetname>
46   //
47   IScsiRootPathIdLen = (UINT8) AsciiStrLen (ISCSI_ROOT_PATH_ID);
48 
49   if ((Length <= IScsiRootPathIdLen) || (CompareMem (RootPath, ISCSI_ROOT_PATH_ID, IScsiRootPathIdLen) != 0)) {
50     return EFI_NOT_FOUND;
51   }
52   //
53   // Skip the iSCSI RootPath ID "iscsi:".
54   //
55   RootPath += IScsiRootPathIdLen;
56   Length  = (UINT8) (Length - IScsiRootPathIdLen);
57 
58   TmpStr  = (CHAR8 *) AllocatePool (Length + 1);
59   if (TmpStr == NULL) {
60     return EFI_OUT_OF_RESOURCES;
61   }
62 
63   CopyMem (TmpStr, RootPath, Length);
64   TmpStr[Length]  = '\0';
65 
66   Index           = 0;
67   FieldIndex      = RP_FIELD_IDX_SERVERNAME;
68   ZeroMem (&Fields[0], sizeof (Fields));
69 
70   //
71   // Extract the fields in the Root Path option string.
72   //
73   for (FieldIndex = RP_FIELD_IDX_SERVERNAME; (FieldIndex < RP_FIELD_IDX_MAX) && (Index < Length); FieldIndex++) {
74     if (TmpStr[Index] != ISCSI_ROOT_PATH_FIELD_DELIMITER) {
75       Fields[FieldIndex].Str = &TmpStr[Index];
76     }
77 
78     while ((TmpStr[Index] != ISCSI_ROOT_PATH_FIELD_DELIMITER) && (Index < Length)) {
79       Index++;
80     }
81 
82     if (TmpStr[Index] == ISCSI_ROOT_PATH_FIELD_DELIMITER) {
83       if (FieldIndex != RP_FIELD_IDX_TARGETNAME) {
84         TmpStr[Index] = '\0';
85         Index++;
86       }
87 
88       if (Fields[FieldIndex].Str != NULL) {
89         Fields[FieldIndex].Len = (UINT8) AsciiStrLen (Fields[FieldIndex].Str);
90       }
91     }
92   }
93 
94   if (FieldIndex != RP_FIELD_IDX_MAX) {
95     Status = EFI_INVALID_PARAMETER;
96     goto ON_EXIT;
97   }
98 
99     if ((Fields[RP_FIELD_IDX_SERVERNAME].Str == NULL) ||
100       (Fields[RP_FIELD_IDX_TARGETNAME].Str == NULL) ||
101       (Fields[RP_FIELD_IDX_PROTOCOL].Len > 1)
102       ) {
103 
104     Status = EFI_INVALID_PARAMETER;
105     goto ON_EXIT;
106   }
107   //
108   // Get the IP address of the target.
109   //
110   Field   = &Fields[RP_FIELD_IDX_SERVERNAME];
111   Status  = IScsiAsciiStrToIp (Field->Str, &ConfigNvData->TargetIp);
112   if (EFI_ERROR (Status)) {
113     goto ON_EXIT;
114   }
115   //
116   // Check the protocol type.
117   //
118   Field = &Fields[RP_FIELD_IDX_PROTOCOL];
119   if ((Field->Str != NULL) && ((*(Field->Str) - '0') != EFI_IP_PROTO_TCP)) {
120     Status = EFI_INVALID_PARAMETER;
121     goto ON_EXIT;
122   }
123   //
124   // Get the port of the iSCSI target.
125   //
126   Field = &Fields[RP_FIELD_IDX_PORT];
127   if (Field->Str != NULL) {
128     ConfigNvData->TargetPort = (UINT16) AsciiStrDecimalToUintn (Field->Str);
129   } else {
130     ConfigNvData->TargetPort = ISCSI_WELL_KNOWN_PORT;
131   }
132   //
133   // Get the LUN.
134   //
135   Field = &Fields[RP_FIELD_IDX_LUN];
136   if (Field->Str != NULL) {
137     Status = IScsiAsciiStrToLun (Field->Str, ConfigNvData->BootLun);
138     if (EFI_ERROR (Status)) {
139       goto ON_EXIT;
140     }
141   } else {
142     ZeroMem (ConfigNvData->BootLun, sizeof (ConfigNvData->BootLun));
143   }
144   //
145   // Get the target iSCSI Name.
146   //
147   Field = &Fields[RP_FIELD_IDX_TARGETNAME];
148 
149   if (AsciiStrLen (Field->Str) > ISCSI_NAME_MAX_SIZE - 1) {
150     Status = EFI_INVALID_PARAMETER;
151     goto ON_EXIT;
152   }
153   //
154   // Validate the iSCSI name.
155   //
156   Status = IScsiNormalizeName (Field->Str, AsciiStrLen (Field->Str));
157   if (EFI_ERROR (Status)) {
158     goto ON_EXIT;
159   }
160 
161   AsciiStrCpyS (ConfigNvData->TargetName, ISCSI_NAME_MAX_SIZE, Field->Str);
162 
163 ON_EXIT:
164 
165   FreePool (TmpStr);
166 
167   return Status;
168 }
169 
170 /**
171   The callback function registerd to the DHCP4 instance which is used to select
172   the qualified DHCP OFFER.
173 
174   @param[in]  This         The DHCP4 protocol.
175   @param[in]  Context      The context set when configuring the DHCP4 protocol.
176   @param[in]  CurrentState The current state of the DHCP4 protocol.
177   @param[in]  Dhcp4Event   The event occurs in the current state.
178   @param[in]  Packet       The DHCP packet that is to be sent or already received.
179   @param[out] NewPacket    The packet used to replace the above Packet.
180 
181   @retval EFI_SUCCESS      Either the DHCP OFFER is qualified or we're not intereseted
182                            in the Dhcp4Event.
183   @retval EFI_NOT_READY    The DHCP OFFER packet doesn't match our requirements.
184   @retval Others           Other errors as indicated.
185 **/
186 EFI_STATUS
187 EFIAPI
IScsiDhcpSelectOffer(IN EFI_DHCP4_PROTOCOL * This,IN VOID * Context,IN EFI_DHCP4_STATE CurrentState,IN EFI_DHCP4_EVENT Dhcp4Event,IN EFI_DHCP4_PACKET * Packet,OPTIONAL OUT EFI_DHCP4_PACKET ** NewPacket OPTIONAL)188 IScsiDhcpSelectOffer (
189   IN  EFI_DHCP4_PROTOCOL  * This,
190   IN  VOID                *Context,
191   IN  EFI_DHCP4_STATE     CurrentState,
192   IN  EFI_DHCP4_EVENT     Dhcp4Event,
193   IN  EFI_DHCP4_PACKET    * Packet, OPTIONAL
194   OUT EFI_DHCP4_PACKET    **NewPacket OPTIONAL
195   )
196 {
197   EFI_STATUS              Status;
198   UINT32                  OptionCount;
199   EFI_DHCP4_PACKET_OPTION **OptionList;
200   UINT32                  Index;
201 
202   if ((Dhcp4Event != Dhcp4RcvdOffer) && (Dhcp4Event != Dhcp4SelectOffer)) {
203     return EFI_SUCCESS;
204   }
205 
206   OptionCount = 0;
207 
208   Status      = This->Parse (This, Packet, &OptionCount, NULL);
209   if (Status != EFI_BUFFER_TOO_SMALL) {
210     return EFI_NOT_READY;
211   }
212 
213   OptionList = AllocatePool (OptionCount * sizeof (EFI_DHCP4_PACKET_OPTION *));
214   if (OptionList == NULL) {
215     return EFI_NOT_READY;
216   }
217 
218   Status = This->Parse (This, Packet, &OptionCount, OptionList);
219   if (EFI_ERROR (Status)) {
220     FreePool (OptionList);
221     return EFI_NOT_READY;
222   }
223 
224   for (Index = 0; Index < OptionCount; Index++) {
225     if (OptionList[Index]->OpCode != DHCP4_TAG_ROOTPATH) {
226       continue;
227     }
228 
229     Status = IScsiDhcpExtractRootPath (
230               (CHAR8 *) &OptionList[Index]->Data[0],
231               OptionList[Index]->Length,
232               (ISCSI_SESSION_CONFIG_NVDATA *) Context
233               );
234 
235     break;
236   }
237 
238   if (Index == OptionCount) {
239     Status = EFI_NOT_READY;
240   }
241 
242   FreePool (OptionList);
243 
244   return Status;
245 }
246 
247 /**
248   Parse the DHCP ACK to get the address configuration and DNS information.
249 
250   @param[in]       Dhcp4        The DHCP4 protocol.
251   @param[in, out]  ConfigData   The session configuration data.
252 
253   @retval EFI_SUCCESS           The DNS information is got from the DHCP ACK.
254   @retval EFI_NO_MAPPING        DHCP failed to acquire address and other information.
255   @retval EFI_INVALID_PARAMETER The DHCP ACK's DNS option is mal-formatted.
256   @retval EFI_DEVICE_ERROR      Other errors as indicated.
257 **/
258 EFI_STATUS
IScsiParseDhcpAck(IN EFI_DHCP4_PROTOCOL * Dhcp4,IN OUT ISCSI_SESSION_CONFIG_DATA * ConfigData)259 IScsiParseDhcpAck (
260   IN      EFI_DHCP4_PROTOCOL         *Dhcp4,
261   IN OUT  ISCSI_SESSION_CONFIG_DATA  *ConfigData
262   )
263 {
264   EFI_STATUS              Status;
265   EFI_DHCP4_MODE_DATA     Dhcp4ModeData;
266   UINT32                  OptionCount;
267   EFI_DHCP4_PACKET_OPTION **OptionList;
268   UINT32                  Index;
269 
270   Status = Dhcp4->GetModeData (Dhcp4, &Dhcp4ModeData);
271   if (EFI_ERROR (Status)) {
272     return Status;
273   }
274 
275   if (Dhcp4ModeData.State != Dhcp4Bound) {
276     return EFI_NO_MAPPING;
277   }
278 
279   CopyMem (&ConfigData->NvData.LocalIp, &Dhcp4ModeData.ClientAddress, sizeof (EFI_IPv4_ADDRESS));
280   CopyMem (&ConfigData->NvData.SubnetMask, &Dhcp4ModeData.SubnetMask, sizeof (EFI_IPv4_ADDRESS));
281   CopyMem (&ConfigData->NvData.Gateway, &Dhcp4ModeData.RouterAddress, sizeof (EFI_IPv4_ADDRESS));
282 
283   OptionCount = 0;
284   OptionList  = NULL;
285 
286   Status      = Dhcp4->Parse (Dhcp4, Dhcp4ModeData.ReplyPacket, &OptionCount, OptionList);
287   if (Status != EFI_BUFFER_TOO_SMALL) {
288     return EFI_DEVICE_ERROR;
289   }
290 
291   OptionList = AllocatePool (OptionCount * sizeof (EFI_DHCP4_PACKET_OPTION *));
292   if (OptionList == NULL) {
293     return EFI_OUT_OF_RESOURCES;
294   }
295 
296   Status = Dhcp4->Parse (Dhcp4, Dhcp4ModeData.ReplyPacket, &OptionCount, OptionList);
297   if (EFI_ERROR (Status)) {
298     FreePool (OptionList);
299     return EFI_DEVICE_ERROR;
300   }
301 
302   for (Index = 0; Index < OptionCount; Index++) {
303     //
304     // Get DNS server addresses and DHCP server address from this offer.
305     //
306     if (OptionList[Index]->OpCode == DHCP4_TAG_DNS_SERVER) {
307 
308       if (((OptionList[Index]->Length & 0x3) != 0) || (OptionList[Index]->Length == 0)) {
309         Status = EFI_INVALID_PARAMETER;
310         break;
311       }
312       //
313       // Primary DNS server address.
314       //
315       CopyMem (&ConfigData->PrimaryDns, &OptionList[Index]->Data[0], sizeof (EFI_IPv4_ADDRESS));
316 
317       if (OptionList[Index]->Length > 4) {
318         //
319         // Secondary DNS server address
320         //
321         CopyMem (&ConfigData->SecondaryDns, &OptionList[Index]->Data[4], sizeof (EFI_IPv4_ADDRESS));
322       }
323     } else if (OptionList[Index]->OpCode == DHCP4_TAG_SERVER_ID) {
324       if (OptionList[Index]->Length != 4) {
325         Status = EFI_INVALID_PARAMETER;
326         break;
327       }
328 
329       CopyMem (&ConfigData->DhcpServer, &OptionList[Index]->Data[0], sizeof (EFI_IPv4_ADDRESS));
330     }
331   }
332 
333   FreePool (OptionList);
334 
335   return Status;
336 }
337 
338 /**
339   Parse the DHCP ACK to get the address configuration and DNS information.
340 
341   @param[in]       Image            The handle of the driver image.
342   @param[in]       Controller       The handle of the controller;
343   @param[in, out]  ConfigData       The session configuration data.
344 
345   @retval EFI_SUCCESS           The DNS information is got from the DHCP ACK.
346   @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory.
347   @retval EFI_NO_MEDIA          There was a media error.
348   @retval Others                Other errors as indicated.
349 
350 **/
351 EFI_STATUS
IScsiDoDhcp(IN EFI_HANDLE Image,IN EFI_HANDLE Controller,IN OUT ISCSI_SESSION_CONFIG_DATA * ConfigData)352 IScsiDoDhcp (
353   IN     EFI_HANDLE                 Image,
354   IN     EFI_HANDLE                 Controller,
355   IN OUT ISCSI_SESSION_CONFIG_DATA  *ConfigData
356   )
357 {
358   EFI_HANDLE              Dhcp4Handle;
359   EFI_DHCP4_PROTOCOL      *Dhcp4;
360   EFI_STATUS              Status;
361   EFI_DHCP4_PACKET_OPTION *ParaList;
362   EFI_DHCP4_CONFIG_DATA   Dhcp4ConfigData;
363   BOOLEAN                 MediaPresent;
364   UINT8                   *Data;
365 
366   Dhcp4Handle = NULL;
367   Dhcp4       = NULL;
368   ParaList    = NULL;
369 
370   //
371   // Check media status before do DHCP
372   //
373   MediaPresent = TRUE;
374   NetLibDetectMedia (Controller, &MediaPresent);
375   if (!MediaPresent) {
376     return EFI_NO_MEDIA;
377   }
378 
379   //
380   // Create a DHCP4 child instance and get the protocol.
381   //
382   Status = NetLibCreateServiceChild (
383             Controller,
384             Image,
385             &gEfiDhcp4ServiceBindingProtocolGuid,
386             &Dhcp4Handle
387             );
388   if (EFI_ERROR (Status)) {
389     return Status;
390   }
391 
392   Status = gBS->OpenProtocol (
393                   Dhcp4Handle,
394                   &gEfiDhcp4ProtocolGuid,
395                   (VOID **)&Dhcp4,
396                   Image,
397                   Controller,
398                   EFI_OPEN_PROTOCOL_BY_DRIVER
399                   );
400   if (EFI_ERROR (Status)) {
401     goto ON_EXIT;
402   }
403 
404   ParaList = AllocatePool (sizeof (EFI_DHCP4_PACKET_OPTION) + 3);
405   if (ParaList == NULL) {
406     Status = EFI_OUT_OF_RESOURCES;
407     goto ON_EXIT;
408   }
409   //
410   // Ask the server to reply with Netmask, Router, DNS and RootPath options.
411   //
412   ParaList->OpCode  = DHCP4_TAG_PARA_LIST;
413   ParaList->Length  = (UINT8) (ConfigData->NvData.TargetInfoFromDhcp ? 4 : 3);
414   Data = &ParaList->Data[0];
415   Data[0] = DHCP4_TAG_NETMASK;
416   Data[1] = DHCP4_TAG_ROUTER;
417   Data[2] = DHCP4_TAG_DNS_SERVER;
418   Data[3] = DHCP4_TAG_ROOTPATH;
419 
420   ZeroMem (&Dhcp4ConfigData, sizeof (EFI_DHCP4_CONFIG_DATA));
421   Dhcp4ConfigData.OptionCount = 1;
422   Dhcp4ConfigData.OptionList  = &ParaList;
423 
424   if (ConfigData->NvData.TargetInfoFromDhcp) {
425     //
426     // Use callback to select an offer which contains target information.
427     //
428     Dhcp4ConfigData.Dhcp4Callback   = IScsiDhcpSelectOffer;
429     Dhcp4ConfigData.CallbackContext = &ConfigData->NvData;
430   }
431 
432   Status = Dhcp4->Configure (Dhcp4, &Dhcp4ConfigData);
433   if (EFI_ERROR (Status)) {
434     goto ON_EXIT;
435   }
436 
437   Status = Dhcp4->Start (Dhcp4, NULL);
438   if (EFI_ERROR (Status)) {
439     goto ON_EXIT;
440   }
441   //
442   // Parse the ACK to get required information.
443   //
444   Status = IScsiParseDhcpAck (Dhcp4, ConfigData);
445 
446 ON_EXIT:
447 
448   if (ParaList != NULL) {
449     FreePool (ParaList);
450   }
451 
452   if (Dhcp4 != NULL) {
453     Dhcp4->Stop (Dhcp4);
454     Dhcp4->Configure (Dhcp4, NULL);
455 
456     gBS->CloseProtocol (
457           Dhcp4Handle,
458           &gEfiDhcp4ProtocolGuid,
459           Image,
460           Controller
461           );
462   }
463 
464   NetLibDestroyServiceChild (
465     Controller,
466     Image,
467     &gEfiDhcp4ServiceBindingProtocolGuid,
468     Dhcp4Handle
469     );
470 
471   return Status;
472 }
473