-
Notifications
You must be signed in to change notification settings - Fork 180
Expand file tree
/
Copy pathdpc.go
More file actions
1480 lines (1348 loc) · 51.7 KB
/
dpc.go
File metadata and controls
1480 lines (1348 loc) · 51.7 KB
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
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2023 Zededa, Inc.
// SPDX-License-Identifier: Apache-2.0
package types
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"os"
"reflect"
"time"
"github.com/google/go-cmp/cmp"
"github.com/lf-edge/eve-api/go/evecommon"
"github.com/lf-edge/eve/pkg/pillar/base"
"github.com/lf-edge/eve/pkg/pillar/utils/generics"
"github.com/lf-edge/eve/pkg/pillar/utils/netutils"
uuid "github.com/satori/go.uuid"
"google.golang.org/protobuf/types/known/timestamppb"
)
// DevicePortConfigVersion is used to track major changes in DPC semantics.
type DevicePortConfigVersion uint32
// When new fields and/or new semantics are added to DevicePortConfig a new
// version value is added here.
const (
DPCInitial DevicePortConfigVersion = iota
DPCIsMgmt // Require IsMgmt to be set for management ports
)
// DPCState tracks the progression a DPC verification.
type DPCState uint8
const (
// DPCStateNone : undefined state.
DPCStateNone DPCState = iota
// DPCStateFail : DPC verification failed.
DPCStateFail
// DPCStateFailWithIPAndDNS : failed to reach controller but has IP/DNS.
DPCStateFailWithIPAndDNS
// DPCStateSuccess : DPC verification succeeded.
DPCStateSuccess
// DPCStateIPDNSWait : waiting for interface IP address(es) and/or DNS server(s).
DPCStateIPDNSWait
// DPCStatePCIWait : waiting for some interface to come from pciback.
DPCStatePCIWait
// DPCStateIntfWait : waiting for some interface to appear in the network stack.
DPCStateIntfWait
// DPCStateRemoteWait : DPC verification failed because controller is down
// or has old certificate.
DPCStateRemoteWait
// DPCStateAsyncWait : waiting for some config operations to finalize which are
// running asynchronously in the background.
DPCStateAsyncWait
// DPCStateWwanWait : waiting for the wwan microservice to apply the latest
// cellular configuration.
DPCStateWwanWait
)
// String returns the string name
func (status DPCState) String() string {
switch status {
case DPCStateNone:
return ""
case DPCStateFail:
return "DPC_FAIL"
case DPCStateFailWithIPAndDNS:
return "DPC_FAIL_WITH_IPANDDNS"
case DPCStateSuccess:
return "DPC_SUCCESS"
case DPCStateIPDNSWait:
return "DPC_IPDNS_WAIT"
case DPCStatePCIWait:
return "DPC_PCI_WAIT"
case DPCStateIntfWait:
return "DPC_INTF_WAIT"
case DPCStateRemoteWait:
return "DPC_REMOTE_WAIT"
case DPCStateAsyncWait:
return "DPC_ASYNC_WAIT"
case DPCStateWwanWait:
return "DPC_WWAN_WAIT"
default:
return fmt.Sprintf("Unknown status %d", status)
}
}
// Describe returns a short human-readable description of the current DPC state.
func (status DPCState) Describe() string {
switch status {
case DPCStateNone:
return "undefined state"
case DPCStateFail:
return "DPC verification failed"
case DPCStateFailWithIPAndDNS:
return "DPC verification failed, but interface has IP and DNS"
case DPCStateSuccess:
return "DPC verification succeeded"
case DPCStateIPDNSWait:
return "waiting for interface IP address(es) and/or DNS server(s)"
case DPCStatePCIWait:
return "waiting for interface from pciback"
case DPCStateIntfWait:
return "waiting for interface to appear in network stack"
case DPCStateRemoteWait:
return "controller encountered an internal error or is using an outdated certificate"
case DPCStateAsyncWait:
return "waiting for asynchronous config operations to complete"
case DPCStateWwanWait:
return "waiting for wwan microservice to apply cellular configuration"
default:
return fmt.Sprintf("unknown state %d", status)
}
}
// InProgress returns true if the DPC verification is still in progress
// (i.e., the state is not final).
func (status DPCState) InProgress() bool {
switch status {
case DPCStateIPDNSWait,
DPCStatePCIWait,
DPCStateIntfWait,
DPCStateAsyncWait,
DPCStateWwanWait:
return true
case DPCStateNone,
DPCStateFail,
DPCStateFailWithIPAndDNS,
DPCStateSuccess,
// Although we wait for the controller to be fixed, connectivity testing
// has actually completed.
DPCStateRemoteWait:
return false
default:
return false
}
}
const (
// PortCostMin is the lowest cost
PortCostMin = uint8(0)
// PortCostMax is the highest cost
PortCostMax = uint8(255)
)
const (
// DefaultMTU : the default Ethernet MTU of 1500 bytes.
DefaultMTU = 1500
// MinMTU : minimum accepted MTU value.
// As per RFC 8200, the MTU must not be less than 1280 bytes to accommodate IPv6 packets.
MinMTU = 1280
// MaxMTU : maximum accepted MTU value.
// The Total Length field of IPv4 and the Payload Length field of IPv6 each have a size
// of 16 bits, thus allowing data of up to 65535 octets.
// For now, we will not support IPv6 jumbograms.
MaxMTU = 65535
)
const (
// LastResortKey : key used for the Last-Resort DPC.
LastResortKey = "lastresort"
// ManualDPCKey : key used for DPC submitted from TUI.
ManualDPCKey = "manual"
// LpsDPCKey : key used for DPC containing local configuration changes submitted by LPS.
LpsDPCKey = "lps"
)
// DevicePortConfig is a misnomer in that it includes the total test results
// plus the test results for a given port. The complete status with
// IP addresses lives in DeviceNetworkStatus
type DevicePortConfig struct {
Version DevicePortConfigVersion `json:",omitempty"`
Key string `json:",omitempty"`
TimePriority time.Time `json:",omitempty"` // All zero's is fallback lowest priority
State DPCState `json:",omitempty"`
ShaFile string `json:",omitempty"` // File in which to write ShaValue once DevicePortConfigList published
ShaValue []byte `json:",omitempty"`
TestResults
LastIPAndDNS time.Time `json:",omitempty"` // Time when we got some IP addresses and DNS
Ports []NetworkPortConfig `json:",omitempty"`
}
// PubKey is used for pubsub. Key string plus TimePriority
func (config DevicePortConfig) PubKey() string {
return config.Key + "@" + config.TimePriority.UTC().Format(time.RFC3339Nano)
}
// LogCreate :
func (config DevicePortConfig) LogCreate(logBase *base.LogObject) {
logObject := base.NewLogObject(logBase, base.DevicePortConfigLogType, "",
nilUUID, config.LogKey())
if logObject == nil {
return
}
logObject.CloneAndAddField("ports-int64", len(config.Ports)).
AddField("last-failed", config.LastFailed).
AddField("last-succeeded", config.LastSucceeded).
AddField("last-error", config.LastError).
AddField("last-warning", config.LastWarning).
AddField("state", config.State.String()).
Noticef("DevicePortConfig create")
for _, p := range config.Ports {
// XXX different logobject for a particular port?
logObject.CloneAndAddField("ifname", p.IfName).
AddField("last-error", p.LastError).
AddField("last-warning", p.LastWarning).
AddField("last-succeeded", p.LastSucceeded).
AddField("last-failed", p.LastFailed).
Noticef("DevicePortConfig port create")
}
}
// LogModify :
func (config DevicePortConfig) LogModify(logBase *base.LogObject, old interface{}) {
logObject := base.EnsureLogObject(logBase, base.DevicePortConfigLogType, "",
nilUUID, config.LogKey())
oldConfig, ok := old.(DevicePortConfig)
if !ok {
logObject.Clone().Fatalf("LogModify: Old object interface passed is not of DevicePortConfig type")
}
if len(oldConfig.Ports) != len(config.Ports) ||
oldConfig.LastFailed != config.LastFailed ||
oldConfig.LastSucceeded != config.LastSucceeded ||
oldConfig.LastError != config.LastError ||
oldConfig.LastWarning != config.LastWarning ||
oldConfig.State != config.State {
logData := logObject.CloneAndAddField("ports-int64", len(config.Ports)).
AddField("last-failed", config.LastFailed).
AddField("last-succeeded", config.LastSucceeded).
AddField("last-error", config.LastError).
AddField("last-warning", config.LastWarning).
AddField("state", config.State.String()).
AddField("old-ports-int64", len(oldConfig.Ports)).
AddField("old-last-failed", oldConfig.LastFailed).
AddField("old-last-succeeded", oldConfig.LastSucceeded).
AddField("old-last-error", oldConfig.LastError).
AddField("old-last-warning", oldConfig.LastWarning).
AddField("old-state", oldConfig.State.String())
if len(oldConfig.Ports) == len(config.Ports) &&
config.LastFailed == oldConfig.LastFailed &&
config.LastError == oldConfig.LastError &&
config.LastWarning == oldConfig.LastWarning &&
oldConfig.State == config.State &&
config.LastSucceeded.After(oldConfig.LastFailed) &&
oldConfig.LastSucceeded.After(oldConfig.LastFailed) {
// if we have success again, reduce log level
logData.Function("DevicePortConfig modify")
} else {
logData.Notice("DevicePortConfig modify")
}
}
// XXX which fields to compare/log?
for i, p := range config.Ports {
if len(oldConfig.Ports) <= i {
continue
}
op := oldConfig.Ports[i]
// XXX different logobject for a particular port?
if p.HasError() != op.HasError() ||
p.LastFailed != op.LastFailed ||
p.LastSucceeded != op.LastSucceeded ||
p.LastError != op.LastError ||
p.LastWarning != op.LastWarning {
logData := logObject.CloneAndAddField("ifname", p.IfName).
AddField("last-error", p.LastError).
AddField("last-warning", p.LastWarning).
AddField("last-succeeded", p.LastSucceeded).
AddField("last-failed", p.LastFailed).
AddField("old-last-error", op.LastError).
AddField("old-last-warning", op.LastWarning).
AddField("old-last-succeeded", op.LastSucceeded).
AddField("old-last-failed", op.LastFailed)
if p.HasError() == op.HasError() &&
p.LastError == op.LastError &&
p.LastWarning == op.LastWarning {
// if we have success or the same error again, reduce log level
logData.Function("DevicePortConfig port modify")
} else {
logData.Notice("DevicePortConfig port modify")
}
}
}
}
// LogDelete :
func (config DevicePortConfig) LogDelete(logBase *base.LogObject) {
logObject := base.EnsureLogObject(logBase, base.DevicePortConfigLogType, "",
nilUUID, config.LogKey())
logObject.CloneAndAddField("ports-int64", len(config.Ports)).
AddField("last-failed", config.LastFailed).
AddField("last-succeeded", config.LastSucceeded).
AddField("last-error", config.LastError).
AddField("last-warning", config.LastWarning).
AddField("state", config.State.String()).
Noticef("DevicePortConfig delete")
for _, p := range config.Ports {
// XXX different logobject for a particular port?
logObject.CloneAndAddField("ifname", p.IfName).
AddField("last-error", p.LastError).
AddField("last-warning", p.LastWarning).
AddField("last-succeeded", p.LastSucceeded).
AddField("last-failed", p.LastFailed).
Noticef("DevicePortConfig port delete")
}
base.DeleteLogObject(logBase, config.LogKey())
}
// LogKey :
func (config DevicePortConfig) LogKey() string {
return string(base.DevicePortConfigLogType) + "-" + config.PubKey()
}
// LookupPortByIfName returns port configuration for the given interface.
func (config *DevicePortConfig) LookupPortByIfName(ifName string) *NetworkPortConfig {
for i := range config.Ports {
port := &config.Ports[i]
if ifName == port.IfName {
return port
}
}
return nil
}
// LookupPortByLogicallabel returns port configuration referenced by the logical label.
func (config *DevicePortConfig) LookupPortByLogicallabel(
label string) *NetworkPortConfig {
for i := range config.Ports {
port := &config.Ports[i]
if port.Logicallabel == label {
return port
}
}
return nil
}
// LookupPortsByLabel returns all port configurations with the given label assigned
// (can be logical label or shared label).
func (config *DevicePortConfig) LookupPortsByLabel(
label string) (ports []*NetworkPortConfig) {
for i := range config.Ports {
port := &config.Ports[i]
if port.Logicallabel == label || generics.ContainsItem(port.SharedLabels, label) {
ports = append(ports, port)
}
}
return ports
}
// RecordPortSuccess - Record for given ifname in PortConfig
func (config *DevicePortConfig) RecordPortSuccess(ifname string) {
portPtr := config.LookupPortByIfName(ifname)
if portPtr != nil {
portPtr.RecordSuccess()
}
}
// RecordPortFailure - Record for given ifname in PortConfig
func (config *DevicePortConfig) RecordPortFailure(ifname string, errStr string) {
portPtr := config.LookupPortByIfName(ifname)
if portPtr != nil {
portPtr.RecordFailure(errStr)
}
}
// IsPortUsedAsVlanParent - returns true if port with the given logical label
// is used as a VLAN parent interface.
func (config DevicePortConfig) IsPortUsedAsVlanParent(portLabel string) bool {
for _, port2 := range config.Ports {
if port2.L2Type == L2LinkTypeVLAN && port2.VLAN.ParentPort == portLabel {
return true
}
}
return false
}
// IsPortAggregatedByBond - returns true if port with the given logical label
// is aggregated by a Bond (LAG).
func (config DevicePortConfig) IsPortAggregatedByBond(portLabel string) bool {
for _, port2 := range config.Ports {
if port2.L2Type == L2LinkTypeBond {
for _, aggrPort := range port2.Bond.AggregatedPorts {
if aggrPort == portLabel {
return true
}
}
}
}
return false
}
// DPCSanitizeArgs : arguments for DevicePortConfig.DoSanitize().
type DPCSanitizeArgs struct {
SanitizeTimePriority bool
SanitizeKey bool
KeyToUseIfEmpty string
SanitizeName bool
SanitizeL3Port bool
SanitizeSharedLabels bool
}
// DoSanitize ensures that some of the DPC attributes that could be missing
// in a user-injected override.json or after an EVE upgrade are filled in.
func (config *DevicePortConfig) DoSanitize(log *base.LogObject, args DPCSanitizeArgs) {
if args.SanitizeKey {
if config.Key == "" {
config.Key = args.KeyToUseIfEmpty
log.Noticef("DoSanitize: Forcing Key for %s TS %v\n",
config.Key, config.TimePriority)
}
}
if args.SanitizeTimePriority {
zeroTime := time.Time{}
if config.TimePriority == zeroTime {
// A json override file should really contain a
// timepriority field so we can determine whether
// it or the information received from the controller
// is more current.
// If we can stat the file we use 1980, otherwise
// we use 1970; using the modify time of the file
// is too unpredictable.
_, err1 := os.Stat(fmt.Sprintf("%s/DevicePortConfig/%s.json",
TmpDirname, config.Key))
_, err2 := os.Stat(fmt.Sprintf("%s/DevicePortConfig/%s.json",
IdentityDirname, config.Key))
if err1 == nil || err2 == nil {
config.TimePriority = time.Date(1980,
time.January, 1, 0, 0, 0, 0, time.UTC)
} else {
config.TimePriority = time.Date(1970,
time.January, 1, 0, 0, 0, 0, time.UTC)
}
log.Warnf("DoSanitize: Forcing TimePriority for %s to %v",
config.Key, config.TimePriority)
}
}
if args.SanitizeName {
// In case Phylabel isn't set we make it match IfName. Ditto for Logicallabel
// XXX still needed?
for i := range config.Ports {
port := &config.Ports[i]
if port.Phylabel == "" {
port.Phylabel = port.IfName
log.Functionf("DoSanitize: Setting missing Phylabel to match ifname for %s ifname %s",
config.Key, port.IfName)
}
if port.Logicallabel == "" {
port.Logicallabel = port.IfName
log.Functionf("DoSanitize: Setting missing Logicallabel to match ifname for %s ifname %s",
config.Key, port.IfName)
}
}
}
if args.SanitizeL3Port {
// IsL3Port flag was introduced to NetworkPortConfig in 7.3.0
// It is used to differentiate between L3 ports (with IP/DNS config)
// and intermediate L2-only ports (bond slaves, VLAN parents, etc.).
// Before 7.3.0, EVE didn't support L2-only adapters and all uplink ports
// were L3 endpoints.
// However, even with VLANs and bonds there has to be at least one L3
// port (L2 adapters are only intermediates with L3 endpoint(s) at the top).
// This means that to support upgrade from older EVE versions,
// we can simply check if there is at least one L3 port, and if not, it means
// that we are dealing with an older persisted/override DPC, where all
// ports should be marked as L3.
var hasL3Port bool
for _, port := range config.Ports {
hasL3Port = hasL3Port || port.IsL3Port
}
if !hasL3Port {
for i := range config.Ports {
config.Ports[i].IsL3Port = true
}
}
}
if args.SanitizeSharedLabels {
// When upgrading from older EVE version or importing override.json,
// shared labels can be missing.
for i := range config.Ports {
config.Ports[i].UpdateEveDefinedSharedLabels()
}
}
}
// CountMgmtPorts returns the number of management ports
// Exclude any broken ones with Dhcp = DhcpTypeNone
// Optionally exclude mgmt ports with invalid config
func (config *DevicePortConfig) CountMgmtPorts(onlyValidConfig bool) int {
count := 0
for _, port := range config.Ports {
if port.IsMgmt && port.Dhcp != DhcpTypeNone &&
!(onlyValidConfig && port.InvalidConfig) {
count++
}
}
return count
}
// MostlyEqual compares two DevicePortConfig but skips things that are
// more of status such as the timestamps and the TestResults
// XXX Compare Version or not?
// We compare the Ports in array order.
func (config *DevicePortConfig) MostlyEqual(config2 *DevicePortConfig) bool {
if config.Key != config2.Key {
return false
}
if len(config.Ports) != len(config2.Ports) {
return false
}
for i, p1 := range config.Ports {
p2 := config2.Ports[i]
if p1.IfName != p2.IfName ||
p1.PCIAddr != p2.PCIAddr ||
p1.USBAddr != p2.USBAddr ||
p1.Phylabel != p2.Phylabel ||
p1.Logicallabel != p2.Logicallabel ||
!generics.EqualSets(p1.SharedLabels, p2.SharedLabels) ||
p1.Alias != p2.Alias ||
p1.IsMgmt != p2.IsMgmt ||
p1.Cost != p2.Cost ||
p1.MTU != p2.MTU ||
p1.AllowLocalModifications != p2.AllowLocalModifications ||
p1.ConfigSource.Origin != p2.ConfigSource.Origin {
return false
}
if !reflect.DeepEqual(p1.DhcpConfig, p2.DhcpConfig) ||
!reflect.DeepEqual(p1.ProxyConfig, p2.ProxyConfig) ||
!reflect.DeepEqual(p1.WirelessCfg, p2.WirelessCfg) {
return false
}
if p1.IgnoreDhcpNtpServers != p2.IgnoreDhcpNtpServers ||
p1.IgnoreDhcpIPAddresses != p2.IgnoreDhcpIPAddresses ||
p1.IgnoreDhcpGateways != p2.IgnoreDhcpGateways ||
p1.IgnoreDhcpDNSConfig != p2.IgnoreDhcpDNSConfig {
return false
}
if p1.PNAC.Enabled != p2.PNAC.Enabled ||
p1.PNAC.CertEnrollmentProfileName != p2.PNAC.CertEnrollmentProfileName ||
p1.PNAC.EAPMethod != p2.PNAC.EAPMethod ||
p1.PNAC.EAPIdentity != p2.PNAC.EAPIdentity ||
!generics.EqualSetsFn(p1.PNAC.CACertPEM, p2.PNAC.CACertPEM, bytes.Equal) {
return false
}
}
return true
}
// IsDPCTestable - Return false if recent failure (less than "minTimeSinceFailure")
// Also returns false if it isn't usable
func (config DevicePortConfig) IsDPCTestable(minTimeSinceFailure time.Duration) bool {
if !config.IsDPCUsable() {
return false
}
if config.LastFailed.IsZero() {
return true
}
if config.LastSucceeded.After(config.LastFailed) {
return true
}
if config.LastFailed.After(time.Now()) {
// Clocks are not in sync - most likely they are still around
// the start of the epoch.
// Network is likely needed to synchronize the clocks using NTP,
// and we should attempt to establish network connectivity using
// any DPC available.
return true
}
return time.Since(config.LastFailed) >= minTimeSinceFailure
}
// IsDPCUntested - returns true if this is something we might want to test now.
// Checks if it is Usable since there is no point in testing unusable things.
func (config DevicePortConfig) IsDPCUntested() bool {
if config.LastFailed.IsZero() && config.LastSucceeded.IsZero() &&
config.IsDPCUsable() {
return true
}
return false
}
// IsDPCUsable - checks whether something is invalid; no management IP
// addresses means it isn't usable hence we return false if none.
func (config DevicePortConfig) IsDPCUsable() bool {
mgmtCount := config.CountMgmtPorts(true)
return mgmtCount > 0
}
// WasDPCWorking - Check if the last results for the DPC was Success
func (config DevicePortConfig) WasDPCWorking() bool {
if config.LastSucceeded.IsZero() {
return false
}
if config.LastSucceeded.After(config.LastFailed) {
return true
}
return false
}
// LastTestTime returns the most recent test time.
func (config DevicePortConfig) LastTestTime() time.Time {
if config.LastFailed.After(config.LastSucceeded) {
return config.LastFailed
}
return config.LastSucceeded
}
// UpdatePortStatusFromIntfStatusMap - Set TestResults for ports in DevicePortConfig to
// those from intfStatusMap. If a port is not found in intfStatusMap, it means
// the port was not tested, so we retain the original TestResults for the port.
func (config *DevicePortConfig) UpdatePortStatusFromIntfStatusMap(
intfStatusMap IntfStatusMap) {
for index := range config.Ports {
portPtr := &config.Ports[index]
tr, ok := intfStatusMap.StatusMap[portPtr.IfName]
if ok {
portPtr.TestResults.Update(tr)
}
// Else - Port not tested hence no change
}
}
// IsAnyPortInPciBack checks if any of the Ports are part of IO bundles which are in PCIback.
//
// If true, it also returns the ifName ( NOT bundle name )
// Also returns whether it is currently used by an application by
// returning a UUID. If the UUID is zero it is in PCIback but available.
// Use filterUnassigned to filter out unassigned ports.
func (config *DevicePortConfig) IsAnyPortInPciBack(
log *base.LogObject, aa *AssignableAdapters, filterUnassigned bool) (bool, string, uuid.UUID) {
if aa == nil {
log.Functionf("IsAnyPortInPciBack: nil aa")
return false, "", uuid.UUID{}
}
log.Functionf("IsAnyPortInPciBack: aa init %t, %d bundles, %d ports",
aa.Initialized, len(aa.IoBundleList), len(config.Ports))
for _, port := range config.Ports {
ioBundle := aa.LookupIoBundleIfName(port.IfName)
if ioBundle == nil {
// It is not guaranteed that all Ports are part of Assignable Adapters
// If not found, the adapter is not capable of being assigned at
// PCI level. So it cannot be in PCI back.
log.Functionf("IsAnyPortInPciBack: ifname %s not found",
port.IfName)
continue
}
if ioBundle.IsPCIBack && (!filterUnassigned || ioBundle.UsedByUUID != nilUUID) {
return true, port.IfName, ioBundle.UsedByUUID
}
}
return false, "", uuid.UUID{}
}
// NetworkPortConfig has the configuration and some status like TestResults
// for one IfName.
// XXX odd to have ParseErrors and/or TestResults here but we don't have
// a corresponding Status struct.
// Note that if fields are added the MostlyEqual function needs to be updated.
type NetworkPortConfig struct {
IfName string `json:",omitempty"`
USBAddr string `json:",omitempty"`
PCIAddr string `json:",omitempty"`
Phylabel string `json:",omitempty"` // Physical name set by controller/model
Logicallabel string `json:",omitempty"` // SystemAdapter's name which is logical label in phyio
// Unlike the logicallabel, which is defined in the device model and unique
// for each port, these user-configurable "shared" labels are potentially
// assigned to multiple ports so that they can be used all together with
// some config object (e.g. multiple ports assigned to NI).
// Some special shared labels, such as "uplink" or "freeuplink", are assigned
// to particular ports automatically.
SharedLabels []string `json:",omitempty"`
Alias string `json:",omitempty"` // From SystemAdapter's alias
// Allow the local operator to make (limited) configuration changes to this
// network adapter using Local Profile Server.
AllowLocalModifications bool `json:",omitempty"`
// NetworkUUID - UUID of the Network Object configured for the port.
NetworkUUID uuid.UUID `json:",omitempty"`
IsMgmt bool `json:",omitempty"` // Used to talk to controller
IsL3Port bool `json:",omitempty"` // True if port is applicable to operate on the network layer
// InvalidConfig is used to flag port config which failed parsing or (static) validation
// checks, such as: malformed IP address, undefined required field, IP address not inside
// the subnet, etc.
InvalidConfig bool `json:",omitempty"`
Cost uint8 `json:",omitempty"` // Zero is free
MTU uint16 `json:",omitempty"`
DhcpConfig
ProxyConfig
L2LinkConfig
WirelessCfg WirelessConfig `json:",omitempty"`
PNAC PNACConfig `json:",omitempty"`
// TestResults - Errors from parsing plus success/failure from testing
TestResults
IgnoreDhcpNtpServers bool `json:",omitempty"` // Ignore NTP servers from DHCP
IgnoreDhcpIPAddresses bool `json:",omitempty"` // Ignore IP addresses from DHCP
IgnoreDhcpGateways bool `json:",omitempty"` // Ignore gateways from DHCP
IgnoreDhcpDNSConfig bool `json:",omitempty"` // Ignore DNS config (DomainName + DNSServers) from DHCP
ConfigSource PortConfigSource `json:",omitempty"`
}
// EVE-defined port labels.
const (
// AllPortsLabel references all device ports.
AllPortsLabel = "all"
// UplinkLabel references all management ports.
UplinkLabel = "uplink"
// FreeUplinkLabel references all management ports with 0 cost.
FreeUplinkLabel = "freeuplink"
)
// NetworkConfigOrigin enumerates all possible origins of a network port configuration.
// The values directly match those from the corresponding proto definition
// (evecommon.NetworkConfigOrigin).
type NetworkConfigOrigin uint8
const (
// NetworkConfigOriginUnspecified : unknown or unset origin.
NetworkConfigOriginUnspecified NetworkConfigOrigin = iota
// NetworkConfigOriginController : config received from the controller.
NetworkConfigOriginController
// NetworkConfigOriginBootstrap : initial device config embedded in the EVE installer,
// signed by the controller.
NetworkConfigOriginBootstrap
// NetworkConfigOriginOverride : manually created JSON config injected into
// the installer.
NetworkConfigOriginOverride
// NetworkConfigOriginLastResort : fallback configuration automatically generated
// to enable DHCP on all Ethernet ports when no network config is available or when
// "network.fallback.any.eth" is enabled and none of the existing configs provide
// controller connectivity.
NetworkConfigOriginLastResort
// NetworkConfigOriginTUI : config entered via the terminal UI (TUI).
NetworkConfigOriginTUI
// NetworkConfigOriginLOC : configuration signed by the controller and delivered
// through the Local Operator Console (LOC) in an air-gapped environment.
NetworkConfigOriginLOC
// NetworkConfigOriginLPS : config changes made locally through the Local Profile
// Server (LPS).
NetworkConfigOriginLPS
)
// PortConfigSource describes the origin of the configuration used for a network port.
// It helps distinguish between controller-provided, locally-modified, or initial configs.
type PortConfigSource struct {
// Indicates where EVE obtained the network config.
Origin NetworkConfigOrigin `json:",omitempty"`
// Timestamp when the port’s configuration was originally submitted
// or created at its source.
//
// Meaning depends on the origin:
// - Controller, LOC, bootstrap: timestamp provided by the controller.
// - Local override.json: when the file was first loaded by EVE.
// - Last-resort: when EVE generated the fallback config.
// - LPS modifications: when the modified config was received.
// - TUI: when the user submitted the configuration.
//
// This is different from DevicePortConfig.TimePriority, which is used
// to compare and prioritize different sources of network configuration
// (and may be synthetic for some sources, e.g. year 2000 for override.json,
// epoch 0 for last-resort). By contrast, SubmittedAt represents when the
// port’s configuration first came into existence, regardless of when or whether
// the device applied it.
//
// Note: this is a per-port timestamp. The underlying network config may
// cover multiple ports, but only the portion relevant to this port is
// reflected here.
SubmittedAt time.Time `json:",omitempty"`
}
// ToProto converts PortConfigSource into its protobuf representation.
func (src PortConfigSource) ToProto() *evecommon.PortConfigSource {
return &evecommon.PortConfigSource{
Origin: evecommon.NetworkConfigOrigin(src.Origin),
SubmittedAt: timestamppb.New(src.SubmittedAt),
}
}
// Equal compares two PortConfigSource instances for equality.
func (src PortConfigSource) Equal(src2 PortConfigSource) bool {
return src.Origin == src2.Origin &&
src.SubmittedAt.Equal(src2.SubmittedAt)
}
// IsEveDefinedPortLabel returns true if the given port label is defined by EVE
// and not by the user.
func IsEveDefinedPortLabel(label string) bool {
switch label {
case AllPortsLabel, UplinkLabel, FreeUplinkLabel:
return true
}
return false
}
// UpdateEveDefinedSharedLabels updates EVE-defined shared labels that this port
// should have based on its properties.
func (port *NetworkPortConfig) UpdateEveDefinedSharedLabels() {
// First remove any EVE-defined shared labels from the list.
isUserLabel := func(label string) bool {
return !IsEveDefinedPortLabel(label)
}
port.SharedLabels = generics.FilterList(port.SharedLabels, isUserLabel)
// (Re-)Add shared labels that this port should have based on its config.
port.SharedLabels = append(port.SharedLabels, AllPortsLabel)
if port.IsMgmt {
port.SharedLabels = append(port.SharedLabels, UplinkLabel)
}
if port.IsMgmt && port.Cost == 0 {
port.SharedLabels = append(port.SharedLabels, FreeUplinkLabel)
}
port.SharedLabels = generics.FilterDuplicates(port.SharedLabels)
}
// DhcpType decides how EVE should obtain IP address for a given network port.
type DhcpType uint8
const (
// DhcpTypeNOOP : DHCP type is undefined.
DhcpTypeNOOP DhcpType = iota
// DhcpTypeStatic : static IP config.
DhcpTypeStatic
// DhcpTypeNone : DHCP passthrough for switch NI
// (between app VIF and external DHCP server).
DhcpTypeNone
// DhcpTypeDeprecated : defined here just to match deprecated value in EVE API.
DhcpTypeDeprecated
// DhcpTypeClient : run a DHCP client to obtain an IP address.
// For IPv6, we also use dhcpcd, but its behavior is RA-driven:
// - dhcpcd listens to Router Advertisement (RA) messages.
// - If the RA contains the M (Managed) flag, it runs stateful DHCPv6 to get an address.
// - If the RA contains the O (Other) flag, it may run stateless DHCPv6 (e.g., for DNS).
// - If neither flag is set, it uses only SLAAC and does not run DHCPv6.
DhcpTypeClient
)
// NetworkType decided IP version(s) that EVE should use for a given network port.
type NetworkType uint8
const (
// NetworkTypeNOOP : network type is undefined.
NetworkTypeNOOP NetworkType = 0
// NetworkTypeIPv4 : IPv4 addresses.
NetworkTypeIPv4 NetworkType = 4
// NetworkTypeIPV6 : IPv6 addresses.
NetworkTypeIPV6 NetworkType = 6
// EVE has been running with Dual stack DHCP behavior with both IPv4 & IPv6 specific networks.
// There can be users who are currently benefiting from this behavior.
// It makes sense to introduce two new types IPv4_ONLY & IPv6_ONLY and allow
// the same family selection from UI for the use cases where only one of the IP families
// is required on management/app-shared adapters.
// NetworkTypeIpv4Only : IPv4 addresses only
NetworkTypeIpv4Only NetworkType = 5
// NetworkTypeIpv6Only : IPv6 addresses only
NetworkTypeIpv6Only NetworkType = 7
// NetworkTypeDualStack : Run with dual stack
NetworkTypeDualStack NetworkType = 8
)
// DhcpConfig : DHCP configuration for network port.
type DhcpConfig struct {
Dhcp DhcpType `json:",omitempty"` // If DhcpTypeStatic use below; if DhcpTypeNone do nothing
// AddrSubnet is in CIDR format (e.g., 192.168.1.44/24).
// It's a string (rather than *net.IPNet) to allow unmarshalling from
// user-edited override.json, since *net.IPNet does not implement
// encoding.TextUnmarshaler. (net.IP does, and is therefore used for
// Gateway and DNSServers)
AddrSubnet string `json:",omitempty"`
Gateway net.IP `json:",omitempty"`
DomainName string `json:",omitempty"`
NTPServers []netutils.HostnameOrIP `json:",omitempty"`
DNSServers []net.IP `json:",omitempty"` // If not set we use Gateway as DNS server
Type NetworkType `json:",omitempty"` // IPv4 or IPv6 or Dual stack
}
// EveOSVendorClassID is the DHCP Vendor Class Identifier (Option 60)
// used by EVE OS to identify itself to DHCP servers, which may use it
// for policy decisions such as address assignment or network access.
// For example, a network may grant access to the EVE controller when
// it detects an EVE device through this vendor class identifier.
const EveOSVendorClassID = "LFEDGE-EVE"
// NetworkProxyType is used to differentiate proxies for different network protocols.
type NetworkProxyType uint8
// Values if these definitions should match the values
// given to the types in zapi.ProxyProto
const (
NetworkProxyTypeHTTP NetworkProxyType = iota
NetworkProxyTypeHTTPS
NetworkProxyTypeSOCKS
NetworkProxyTypeFTP
NetworkProxyTypeNOPROXY
NetworkProxyTypeLAST = 255
)
// ProxyEntry is used to store address of a single network proxy.
type ProxyEntry struct {
Type NetworkProxyType `json:"type,omitempty"`
Server string `json:"server,omitempty"`
Port uint32 `json:"port,omitempty"`
}
// FromProto populates a ProxyEntry from its protobuf representation.
func (pe *ProxyEntry) FromProto(proxy *evecommon.ProxyServer) {
if proxy == nil {
return
}
pe.Server = proxy.Server
pe.Port = proxy.Port
switch proxy.Proto {
case evecommon.ProxyProto_PROXY_HTTP:
pe.Type = NetworkProxyTypeHTTP
case evecommon.ProxyProto_PROXY_HTTPS:
pe.Type = NetworkProxyTypeHTTPS
case evecommon.ProxyProto_PROXY_SOCKS:
pe.Type = NetworkProxyTypeSOCKS
case evecommon.ProxyProto_PROXY_FTP:
pe.Type = NetworkProxyTypeFTP
}
}
// ToProto converts a ProxyEntry to its protobuf representation.
func (pe ProxyEntry) ToProto() *evecommon.ProxyServer {
var protoType evecommon.ProxyProto
switch pe.Type {
case NetworkProxyTypeHTTP:
protoType = evecommon.ProxyProto_PROXY_HTTP
case NetworkProxyTypeHTTPS:
protoType = evecommon.ProxyProto_PROXY_HTTPS
case NetworkProxyTypeSOCKS:
protoType = evecommon.ProxyProto_PROXY_SOCKS
case NetworkProxyTypeFTP:
protoType = evecommon.ProxyProto_PROXY_FTP
default:
protoType = evecommon.ProxyProto_PROXY_OTHER
}
return &evecommon.ProxyServer{
Proto: protoType,
Server: pe.Server,
Port: pe.Port,
}
}
// ProxyConfig : proxy configuration for a network port.
type ProxyConfig struct {
Proxies []ProxyEntry `json:",omitempty"`
Exceptions string `json:",omitempty"`
Pacfile string `json:",omitempty"`
// If Enable is set we use WPAD. If the URL is not set we try
// the various DNS suffixes until we can download a wpad.dat file
NetworkProxyEnable bool `json:",omitempty"` // Enable WPAD
NetworkProxyURL string `json:",omitempty"` // Complete URL i.e., with /wpad.dat
WpadURL string `json:",omitempty"` // The URL determined from DNS
// List of certs which will be added to TLS trust
ProxyCertPEM [][]byte `json:"pubsub-large-ProxyCertPEM"` //nolint:tagliatelle
}
// WifiKeySchemeType - types of key management
type WifiKeySchemeType uint8
// FromProto sets the WifiKeySchemeType from the protobuf value.
// Returns an error if the protobuf value is unrecognized.
func (kt *WifiKeySchemeType) FromProto(protoKeyScheme evecommon.WiFiKeyScheme) error {
switch protoKeyScheme {
case evecommon.WiFiKeyScheme_SchemeNOOP:
*kt = KeySchemeNone
case evecommon.WiFiKeyScheme_WPAPSK:
*kt = KeySchemeWpaPsk
case evecommon.WiFiKeyScheme_WPAEAP:
*kt = KeySchemeWpaEap
default:
*kt = KeySchemeOther
return fmt.Errorf("unrecognized WiFi key scheme: %v", protoKeyScheme)
}
return nil
}
// ToProto converts a WifiKeySchemeType to its protobuf representation.
func (kt WifiKeySchemeType) ToProto() evecommon.WiFiKeyScheme {
switch kt {
case KeySchemeNone:
return evecommon.WiFiKeyScheme_SchemeNOOP
case KeySchemeWpaPsk:
return evecommon.WiFiKeyScheme_WPAPSK
case KeySchemeWpaEap:
return evecommon.WiFiKeyScheme_WPAEAP
default:
return evecommon.WiFiKeyScheme_SchemeNOOP
}
}