summaryrefslogtreecommitdiffstats
path: root/NetworkPkg/IpSecDxe/IpSecImpl.c
blob: 15884ae403e8291aa9f29a6835bdf22409f7cb2b (plain)
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
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
/** @file
  The implementation of IPsec Protocol

  Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "IpSecConfigImpl.h"

EFI_IPSEC_PROTOCOL  mIpSecInstance = { IpSecProcess, NULL, TRUE };

extern LIST_ENTRY   mConfigData[IPsecConfigDataTypeMaximum];

/**
  Check if the specified Address is the Valid Address Range.

  This function checks if the bytes after prefixed length are all Zero in this
  Address. This Address is supposed to point to a range address,  meaning it only
  gives the correct prefixed address.

  @param[in]  IpVersion         The IP version.
  @param[in]  Address           Points to EFI_IP_ADDRESS to be checked.
  @param[in]  PrefixLength      The PrefixeLength of this address.

  @retval     TRUE      The address is a vaild address range.
  @retval     FALSE     The address is not a vaild address range.

**/
BOOLEAN
IpSecValidAddressRange (
  IN UINT8                     IpVersion,
  IN EFI_IP_ADDRESS            *Address,
  IN UINT8                     PrefixLength
  )
{
  UINT8           Div;
  UINT8           Mod;
  UINT8           Mask;
  UINT8           AddrLen;
  UINT8           *Addr;
  EFI_IP_ADDRESS  ZeroAddr;

  if (PrefixLength == 0) {
    return TRUE;
  }

  AddrLen = (UINT8) ((IpVersion == IP_VERSION_4) ? 32 : 128);

  if (AddrLen <= PrefixLength) {
    return FALSE;
  }

  Div   = (UINT8) (PrefixLength / 8);
  Mod   = (UINT8) (PrefixLength % 8);
  Addr  = (UINT8 *) Address;
  ZeroMem (&ZeroAddr, sizeof (EFI_IP_ADDRESS));

  //
  // Check whether the mod part of host scope is zero or not.
  //
  if (Mod > 0) {
    Mask = (UINT8) (0xFF << (8 - Mod));

    if ((Addr[Div] | Mask) != Mask) {
      return FALSE;
    }

    Div++;
  }
  //
  // Check whether the div part of host scope is zero or not.
  //
  if (CompareMem (
        &Addr[Div],
        &ZeroAddr,
        sizeof (EFI_IP_ADDRESS) - Div
        ) != 0) {
    return FALSE;
  }

  return TRUE;
}

/**
  Extrct the Address Range from a Address.

  This function keep the prefix address and zero other part address.

  @param[in]  Address           Point to a specified address.
  @param[in]  PrefixLength      The prefix length.
  @param[out] Range             Contain the return Address Range.

**/
VOID
IpSecExtractAddressRange (
  IN EFI_IP_ADDRESS            *Address,
  IN UINT8                     PrefixLength,
  OUT EFI_IP_ADDRESS           *Range
  )
{
  UINT8 Div;
  UINT8 Mod;
  UINT8 Mask;
  UINT8 *Addr;

  if (PrefixLength == 0) {
    return ;
  }

  Div   = (UINT8) (PrefixLength / 8);
  Mod   = (UINT8) (PrefixLength % 8);
  Addr  = (UINT8 *) Range;

  CopyMem (Range, Address, sizeof (EFI_IP_ADDRESS));

  //
  // Zero the mod part of host scope.
  //
  if (Mod > 0) {
    Mask      = (UINT8) (0xFF << (8 - Mod));
    Addr[Div] = (UINT8) (Addr[Div] & Mask);
    Div++;
  }
  //
  // Zero the div part of host scope.
  //
  ZeroMem (&Addr[Div], sizeof (EFI_IP_ADDRESS) - Div);

}

/**
  Checks if the IP Address in the address range of AddressInfos specified.

  @param[in]  IpVersion         The IP version.
  @param[in]  IpAddr            Point to EFI_IP_ADDRESS to be check.
  @param[in]  AddressInfo       A list of EFI_IP_ADDRESS_INFO that is used to check
                                the IP Address is matched.
  @param[in]  AddressCount      The total numbers of the AddressInfo.

  @retval   TRUE    If the Specified IP Address is in the range of the AddressInfos specified.
  @retval   FALSE   If the Specified IP Address is not in the range of the AddressInfos specified.

**/
BOOLEAN
IpSecMatchIpAddress (
  IN UINT8                     IpVersion,
  IN EFI_IP_ADDRESS            *IpAddr,
  IN EFI_IP_ADDRESS_INFO       *AddressInfo,
  IN UINT32                    AddressCount
  )
{
  EFI_IP_ADDRESS  Range;
  UINT32          Index;
  BOOLEAN         IsMatch;

  IsMatch = FALSE;

  for (Index = 0; Index < AddressCount; Index++) {
    //
    // Check whether the target address is in the address range
    // if it's a valid range of address.
    //
    if (IpSecValidAddressRange (
          IpVersion,
          &AddressInfo[Index].Address,
          AddressInfo[Index].PrefixLength
          )) {
      //
      // Get the range of the target address belongs to.
      //
      ZeroMem (&Range, sizeof (EFI_IP_ADDRESS));
      IpSecExtractAddressRange (
        IpAddr,
        AddressInfo[Index].PrefixLength,
        &Range
        );

      if (CompareMem (
            &Range,
            &AddressInfo[Index].Address,
            sizeof (EFI_IP_ADDRESS)
            ) == 0) {
        //
        // The target address is in the address range.
        //
        IsMatch = TRUE;
        break;
      }
    }

    if (CompareMem (
          IpAddr,
          &AddressInfo[Index].Address,
          sizeof (EFI_IP_ADDRESS)
          ) == 0) {
      //
      // The target address is exact same as the address.
      //
      IsMatch = TRUE;
      break;
    }
  }

  return IsMatch;
}

/**
  Check if the specified Protocol and Prot is supported by the specified SPD Entry.

  This function is the subfunction of IPsecLookUpSpdEntry() that is used to
  check if the sent/received IKE packet has the related SPD entry support.

  @param[in]  Protocol          The Protocol to be checked.
  @param[in]  IpPayload         Point to IP Payload to be check.
  @param[in]  SpdProtocol       The Protocol supported by SPD.
  @param[in]  SpdLocalPort      The Local Port in SPD.
  @param[in]  SpdRemotePort     The Remote Port in SPD.
  @param[in]  IsOutbound        Flag to indicate the is for IKE Packet sending or recieving.

  @retval     TRUE      The Protocol and Port are supported by the SPD Entry.
  @retval     FALSE     The Protocol and Port are not supported by the SPD Entry.

**/
BOOLEAN
IpSecMatchNextLayerProtocol (
  IN UINT8                     Protocol,
  IN UINT8                     *IpPayload,
  IN UINT16                    SpdProtocol,
  IN UINT16                    SpdLocalPort,
  IN UINT16                    SpdRemotePort,
  IN BOOLEAN                   IsOutbound
  )
{
  BOOLEAN IsMatch;

  if (SpdProtocol == EFI_IPSEC_ANY_PROTOCOL) {
    return TRUE;
  }

  IsMatch = FALSE;

  if (SpdProtocol == Protocol) {
    switch (Protocol) {
    case EFI_IP_PROTO_UDP:
    case EFI_IP_PROTO_TCP:
      //
      // For udp and tcp, (0, 0) means no need to check local and remote
      // port. The payload is passed from upper level, which means it should
      // be in network order.
      //
      IsMatch = (BOOLEAN) (SpdLocalPort == 0 && SpdRemotePort == 0);
      IsMatch = (BOOLEAN) (IsMatch ||
                           (IsOutbound &&
                           (BOOLEAN)(
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->SrcPort) == SpdLocalPort &&
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->DstPort) == SpdRemotePort
                              )
                            ));

      IsMatch = (BOOLEAN) (IsMatch ||
                           (!IsOutbound &&
                           (BOOLEAN)(
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->DstPort) == SpdLocalPort &&
                              NTOHS (((EFI_UDP_HEADER *) IpPayload)->SrcPort) == SpdRemotePort
                              )
                           ));
      break;

    case EFI_IP_PROTO_ICMP:
      //
      // For icmpv4, type code is replaced with local port and remote port,
      // and (0, 0) means no need to check.
      //
      IsMatch = (BOOLEAN) (SpdLocalPort == 0 && SpdRemotePort == 0);
      IsMatch = (BOOLEAN) (IsMatch ||
                           (BOOLEAN) (((IP4_ICMP_HEAD *) IpPayload)->Type == SpdLocalPort &&
                                      ((IP4_ICMP_HEAD *) IpPayload)->Code == SpdRemotePort
                                      )
                           );
      break;

    case IP6_ICMP:
      //
      // For icmpv6, type code is replaced with local port and remote port,
      // and (0, 0) means no need to check.
      //
      IsMatch = (BOOLEAN) (SpdLocalPort == 0 && SpdRemotePort == 0);

      IsMatch = (BOOLEAN) (IsMatch ||
                           (BOOLEAN) (((IP6_ICMP_HEAD *) IpPayload)->Type == SpdLocalPort &&
                                      ((IP6_ICMP_HEAD *) IpPayload)->Code == SpdRemotePort
                                      )
                          );
      break;

    default:
      IsMatch = TRUE;
      break;
    }
  }

  return IsMatch;
}

/**
  Find the SAD through a specified SPD's SAD list.

  @param[in]  SadList           SAD list related to a specified SPD entry.
  @param[in]  DestAddress       The destination address used to find the SAD entry.

  @return  The pointer to a certain SAD entry.

**/
IPSEC_SAD_ENTRY *
IpSecLookupSadBySpd (
  IN LIST_ENTRY                 *SadList,
  IN EFI_IP_ADDRESS             *DestAddress
  )
{
  LIST_ENTRY      *Entry;
  IPSEC_SAD_ENTRY *SadEntry;

  for (Entry = SadList->ForwardLink; Entry != SadList; Entry = Entry->ForwardLink) {

    SadEntry = IPSEC_SAD_ENTRY_FROM_SPD (Entry);
    //
    // Find the right sad entry which contains the appointed dest address.
    //
    if (CompareMem (
          &SadEntry->Id->DestAddress,
          DestAddress,
          sizeof (EFI_IP_ADDRESS)
          ) == 0) {
      return SadEntry;
    }
  }

  return NULL;
}

/**
  Find the SAD through whole SAD list.

  @param[in]  Spi               The SPI used to search the SAD entry.
  @param[in]  DestAddress       The destination used to search the SAD entry.

  @return  the pointer to a certain SAD entry.

**/
IPSEC_SAD_ENTRY *
IpSecLookupSadBySpi (
  IN UINT32                   Spi,
  IN EFI_IP_ADDRESS           *DestAddress
  )
{
  LIST_ENTRY      *Entry;
  LIST_ENTRY      *SadList;
  IPSEC_SAD_ENTRY *SadEntry;

  SadList = &mConfigData[IPsecConfigDataTypeSad];

  for (Entry = SadList->ForwardLink; Entry != SadList; Entry = Entry->ForwardLink) {

    SadEntry = IPSEC_SAD_ENTRY_FROM_LIST (Entry);
    //
    // Find the right sad entry which contain the appointed spi and dest addr.
    //
    if (SadEntry->Id->Spi == Spi && CompareMem (
                                      &SadEntry->Id->DestAddress,
                                      DestAddress,
                                      sizeof (EFI_IP_ADDRESS)
                                      ) == 0) {

      return SadEntry;
    }
  }

  return NULL;
}

/**
  Look up if there is existing SAD entry for specified IP packet sending.

  This function is called by the IPsecProcess when there is some IP packet needed to
  send out. This function checks if there is an existing SAD entry that can be serviced
  to this IP packet sending. If no existing SAD entry could be used, this
  function will invoke an IPsec Key Exchange Negotiation.

  @param[in]  Private           Points to private data.
  @param[in]  NicHandle         Points to a NIC handle.
  @param[in]  IpVersion         The version of IP.
  @param[in]  IpHead            The IP Header of packet to be sent out.
  @param[in]  IpPayload         The IP Payload to be sent out.
  @param[in]  OldLastHead       The Last protocol of the IP packet.
  @param[in]  SpdEntry          Points to a related SPD entry.
  @param[out] SadEntry          Contains the Point of a related SAD entry.

  @retval EFI_DEVICE_ERROR  One of following conditions is TRUE:
                            - If don't find related UDP service.
                            - Sequence Number is used up.
                            - Extension Sequence Number is used up.
  @retval EFI_DEVICE_ERROR  GC_TODO: Add description for return value.
  @retval EFI_NOT_READY     No existing SAD entry could be used.
  @retval EFI_SUCCESS       Find the related SAD entry.

**/
EFI_STATUS
IpSecLookupSadEntry (
  IN IPSEC_PRIVATE_DATA      *Private,
  IN EFI_HANDLE              NicHandle,
  IN UINT8                   IpVersion,
  IN VOID                    *IpHead,
  IN UINT8                   *IpPayload,
  IN UINT8                   OldLastHead,
  IN IPSEC_SPD_ENTRY         *SpdEntry,
  OUT IPSEC_SAD_ENTRY        **SadEntry
  )
{
  IPSEC_SAD_ENTRY *Entry;
  IPSEC_SAD_DATA  *Data;
  EFI_IP_ADDRESS  DestIp;
  UINT32          SeqNum32;

  *SadEntry   = NULL;
  //
  // Parse the destination address from ip header.
  //
  ZeroMem (&DestIp, sizeof (EFI_IP_ADDRESS));
  if (IpVersion == IP_VERSION_4) {
    CopyMem (
      &DestIp,
      &((IP4_HEAD *) IpHead)->Dst,
      sizeof (IP4_ADDR)
      );
  } else {
    CopyMem (
      &DestIp,
      &((EFI_IP6_HEADER *) IpHead)->DestinationAddress,
      sizeof (EFI_IP_ADDRESS)
      );
  }
  //
  // Find the sad entry in the spd.sas list according to the dest address.
  //
  Entry = IpSecLookupSadBySpd (&SpdEntry->Data->Sas, &DestIp);

  if (Entry == NULL) {

    if (OldLastHead != IP6_ICMP ||
        (OldLastHead == IP6_ICMP && *IpPayload == ICMP_V6_ECHO_REQUEST)
        ) {
      //
      // TODO: Start ike negotiation process except the request packet of ping.
      //
      //IkeNegotiate (UdpService, SpdEntry, &DestIp);
    }

    return EFI_NOT_READY;
  }

  Data = Entry->Data;

  if (!Data->ManualSet) {
    if (Data->ESNEnabled) {
      //
      // Validate the 64bit sn number if 64bit sn enabled.
      //
      if (Data->SequenceNumber + 1 < Data->SequenceNumber) {
        //
        // TODO: Re-negotiate SA
        //
        return EFI_DEVICE_ERROR;
      }
    } else {
      //
      // Validate the 32bit sn number if 64bit sn disabled.
      //
      SeqNum32 = (UINT32) Data->SequenceNumber;
      if (SeqNum32 + 1 < SeqNum32) {
        //
        // TODO: Re-negotiate SA
        //
        return EFI_DEVICE_ERROR;
      }
    }
  }

  *SadEntry = Entry;

  return EFI_SUCCESS;
}

/**
  Find a PAD entry according to a remote IP address.

  @param[in]  IpVersion         The version of IP.
  @param[in]  IpAddr            Points to remote IP address.

  @return the pointer of related PAD entry.

**/
IPSEC_PAD_ENTRY *
IpSecLookupPadEntry (
  IN UINT8                   IpVersion,
  IN EFI_IP_ADDRESS          *IpAddr
  )
{
  LIST_ENTRY          *PadList;
  LIST_ENTRY          *Entry;
  EFI_IP_ADDRESS_INFO *IpAddrInfo;
  IPSEC_PAD_ENTRY     *PadEntry;

  PadList = &mConfigData[IPsecConfigDataTypePad];

  for (Entry = PadList->ForwardLink; Entry != PadList; Entry = Entry->ForwardLink) {

    PadEntry    = IPSEC_PAD_ENTRY_FROM_LIST (Entry);
    IpAddrInfo  = &PadEntry->Id->Id.IpAddress;
    //
    // Find the right pad entry which contain the appointed dest addr.
    //
    if (IpSecMatchIpAddress (IpVersion, IpAddr, IpAddrInfo, 1)) {
      return PadEntry;
    }
  }

  return NULL;
}

/**
  Check if the specified IP packet can be serviced by this SPD entry.

  @param[in]  SpdEntry          Point to SPD entry.
  @param[in]  IpVersion         Version of IP.
  @param[in]  IpHead            Point to IP header.
  @param[in]  IpPayload         Point to IP payload.
  @param[in]  Protocol          The Last protocol of IP packet.
  @param[in]  IsOutbound        Traffic direction.

  @retval EFI_IPSEC_ACTION  The support action of SPD entry.
  @retval -1                If the input packet header doesn't match the SpdEntry.

**/
EFI_IPSEC_ACTION
IpSecLookupSpdEntry (
  IN IPSEC_SPD_ENTRY         *SpdEntry,
  IN UINT8                   IpVersion,
  IN VOID                    *IpHead,
  IN UINT8                   *IpPayload,
  IN UINT8                   Protocol,
  IN BOOLEAN                 IsOutbound
  )
{
  EFI_IPSEC_SPD_SELECTOR  *SpdSel;
  IP4_HEAD                *Ip4;
  EFI_IP6_HEADER          *Ip6;
  EFI_IP_ADDRESS          SrcAddr;
  EFI_IP_ADDRESS          DstAddr;
  BOOLEAN                 SpdMatch;

  ASSERT (SpdEntry != NULL);
  SpdSel  = SpdEntry->Selector;
  Ip4     = (IP4_HEAD *) IpHead;
  Ip6     = (EFI_IP6_HEADER *) IpHead;

  ZeroMem (&SrcAddr, sizeof (EFI_IP_ADDRESS));
  ZeroMem (&DstAddr, sizeof (EFI_IP_ADDRESS));

  //
  // Parse the source and destination address from ip header.
  //
  if (IpVersion == IP_VERSION_4) {
    CopyMem (&SrcAddr, &Ip4->Src, sizeof (IP4_ADDR));
    CopyMem (&DstAddr, &Ip4->Dst, sizeof (IP4_ADDR));
  } else {
    CopyMem (&SrcAddr, &Ip6->SourceAddress, sizeof (EFI_IPv6_ADDRESS));
    CopyMem (&DstAddr, &Ip6->DestinationAddress, sizeof (EFI_IPv6_ADDRESS));
  }
  //
  // Check the local and remote addresses for outbound traffic
  //
  SpdMatch = (BOOLEAN)(IsOutbound &&
                       IpSecMatchIpAddress (
                         IpVersion,
                         &SrcAddr,
                         SpdSel->LocalAddress,
                         SpdSel->LocalAddressCount
                         ) &&
                       IpSecMatchIpAddress (
                         IpVersion,
                         &DstAddr,
                         SpdSel->RemoteAddress,
                         SpdSel->RemoteAddressCount
                         )
                       );

  //
  // Check the local and remote addresses for inbound traffic
  //
  SpdMatch = (BOOLEAN) (SpdMatch ||
                        (!IsOutbound &&
                        IpSecMatchIpAddress (
                          IpVersion,
                          &DstAddr,
                          SpdSel->LocalAddress,
                          SpdSel->LocalAddressCount
                          ) &&
                        IpSecMatchIpAddress (
                          IpVersion,
                          &SrcAddr,
                          SpdSel->RemoteAddress,
                          SpdSel->RemoteAddressCount
                          )
                        ));

  //
  // Check the next layer protocol and local and remote ports.
  //
  SpdMatch = (BOOLEAN) (SpdMatch &&
                        IpSecMatchNextLayerProtocol (
                          Protocol,
                          IpPayload,
                          SpdSel->NextLayerProtocol,
                          SpdSel->LocalPort,
                          SpdSel->RemotePort,
                          IsOutbound
                          )
                        );

  if (SpdMatch) {
    //
    // Find the right spd entry if match the 5 key elements.
    //
    return SpdEntry->Data->Action;
  }

  return (EFI_IPSEC_ACTION) - 1;
}

/**
  Handles IPsec packet processing for inbound and outbound IP packets.

  The EFI_IPSEC_PROCESS process routine handles each inbound or outbound packet.
  The behavior is that it can perform one of the following actions:
  bypass the packet, discard the packet, or protect the packet.

  @param[in]      This             Pointer to the EFI_IPSEC_PROTOCOL instance.
  @param[in]      NicHandle        Instance of the network interface.
  @param[in]      IpVersion        IPV4 or IPV6.
  @param[in, out] IpHead           Pointer to the IP Header.
  @param[in]      LastHead         The protocol of the next layer to be processed by IPsec.
  @param[in]      OptionsBuffer    Pointer to the options buffer.
  @param[in]      OptionsLength    Length of the options buffer.
  @param[in, out] FragmentTable    Pointer to a list of fragments.
  @param[in]      FragmentCount    Number of fragments.
  @param[in]      TrafficDirection Traffic direction.
  @param[out]     RecycleSignal    Event for recycling of resources.

  @retval EFI_SUCCESS              The packet was bypassed and all buffers remain the same.
  @retval EFI_SUCCESS              The packet was protected.
  @retval EFI_ACCESS_DENIED        The packet was discarded.

**/
EFI_STATUS
EFIAPI
IpSecProcess (
  IN     EFI_IPSEC_PROTOCOL              *This,
  IN     EFI_HANDLE                      NicHandle,
  IN     UINT8                           IpVersion,
  IN OUT VOID                            *IpHead,
  IN     UINT8                           *LastHead,
  IN     VOID                            *OptionsBuffer,
  IN     UINT32                          OptionsLength,
  IN OUT EFI_IPSEC_FRAGMENT_DATA         **FragmentTable,
  IN     UINT32                          *FragmentCount,
  IN     EFI_IPSEC_TRAFFIC_DIR           TrafficDirection,
     OUT EFI_EVENT                       *RecycleSignal
  )
{
  IPSEC_PRIVATE_DATA  *Private;
  IPSEC_SPD_ENTRY     *SpdEntry;
  IPSEC_SAD_ENTRY     *SadEntry;
  LIST_ENTRY          *SpdList;
  LIST_ENTRY          *Entry;
  EFI_IPSEC_ACTION    Action;
  EFI_STATUS          Status;
  UINT8               *IpPayload;
  UINT8               OldLastHead;
  BOOLEAN             IsOutbound;

  Private         = IPSEC_PRIVATE_DATA_FROM_IPSEC (This);
  IpPayload       = (*FragmentTable)[0].FragmentBuffer;
  IsOutbound      = (BOOLEAN) ((TrafficDirection == EfiIPsecOutBound) ? TRUE : FALSE);
  OldLastHead     = *LastHead;
  *RecycleSignal  = NULL;

  if (!IsOutbound) {
    //
    // For inbound traffic, process the ipsec header of the packet.
    //
    Status = IpSecProtectInboundPacket (
              IpVersion,
              IpHead,
              LastHead,
              OptionsBuffer,
              OptionsLength,
              FragmentTable,
              FragmentCount,
              &SpdEntry,
              RecycleSignal
              );

    if (Status == EFI_ACCESS_DENIED) {
      //
      // The packet is denied to access.
      //
      goto ON_EXIT;
    }

    if (Status == EFI_SUCCESS) {
      //
      // Check the spd entry if the packet is accessible.
      //
      if (SpdEntry == NULL) {
        Status = EFI_ACCESS_DENIED;
        goto ON_EXIT;
      }
      Action = IpSecLookupSpdEntry (
                SpdEntry,
                IpVersion,
                IpHead,
                IpPayload,
                *LastHead,
                IsOutbound
                );

      if (Action != EfiIPsecActionProtect) {
        //
        // Discard the packet if the spd entry is not protect.
        //
        gBS->SignalEvent (*RecycleSignal);
        *RecycleSignal  = NULL;
        Status          = EFI_ACCESS_DENIED;
      }

      goto ON_EXIT;
    }
  }

  Status  = EFI_ACCESS_DENIED;
  SpdList = &mConfigData[IPsecConfigDataTypeSpd];

  for (Entry = SpdList->ForwardLink; Entry != SpdList; Entry = Entry->ForwardLink) {
    //
    // For outbound and non-ipsec Inbound traffic: check the spd entry.
    //
    SpdEntry = IPSEC_SPD_ENTRY_FROM_LIST (Entry);
    Action = IpSecLookupSpdEntry (
              SpdEntry,
              IpVersion,
              IpHead,
              IpPayload,
              OldLastHead,
              IsOutbound
              );

    switch (Action) {

    case EfiIPsecActionProtect:

      if (IsOutbound) {
        //
        // For outbound traffic, lookup the sad entry.
        //
        Status = IpSecLookupSadEntry (
                  Private,
                  NicHandle,
                  IpVersion,
                  IpHead,
                  IpPayload,
                  OldLastHead,
                  SpdEntry,
                  &SadEntry
                  );

        if (SadEntry != NULL) {
          //
          // Process the packet by the found sad entry.
          //
          Status = IpSecProtectOutboundPacket (
                    IpVersion,
                    IpHead,
                    LastHead,
                    OptionsBuffer,
                    OptionsLength,
                    FragmentTable,
                    FragmentCount,
                    SadEntry,
                    RecycleSignal
                    );

        } else if (OldLastHead == IP6_ICMP && *IpPayload != ICMP_V6_ECHO_REQUEST) {
          //
          // TODO: if no need return not ready to upper layer, change here.
          //
          Status = EFI_SUCCESS;
        }
      } else if (OldLastHead == IP6_ICMP && *IpPayload != ICMP_V6_ECHO_REQUEST) {
        //
        // For inbound icmpv6 traffic except ping request, accept the packet
        // although no sad entry associated with protect spd entry.
        //
        IpSecLookupSadEntry (
          Private,
          NicHandle,
          IpVersion,
          IpHead,
          IpPayload,
          OldLastHead,
          SpdEntry,
          &SadEntry
          );
        if (SadEntry == NULL) {
          Status = EFI_SUCCESS;
        }
      }

      goto ON_EXIT;

    case EfiIPsecActionBypass:
      Status = EFI_SUCCESS;
      goto ON_EXIT;

    case EfiIPsecActionDiscard:
      goto ON_EXIT;

    default:
      //
      // Discard the packet if no spd entry match.
      //
      break;
    }
  }

ON_EXIT:
  return Status;
}