feat: replace networkd with new network implementation
This removes networkd, updates network ready condition, enables all the controllers which were previously disabled. Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
This commit is contained in:
parent
caec3063c8
commit
f2ae9cd0c1
@ -180,6 +180,7 @@ COPY --from=generate-build /api/cluster/*.pb.go /pkg/machinery/api/cluster/
|
||||
COPY --from=generate-build /api/storage/*.pb.go /pkg/machinery/api/storage/
|
||||
COPY --from=generate-build /api/resource/*.pb.go /pkg/machinery/api/resource/
|
||||
COPY --from=generate-build /api/inspect/*.pb.go /pkg/machinery/api/inspect/
|
||||
COPY --from=go-generate /src/pkg/resources/network/ /pkg/resources/network/
|
||||
COPY --from=go-generate /src/pkg/machinery/config/types/v1alpha1/ /pkg/machinery/config/types/v1alpha1/
|
||||
COPY --from=go-generate /src/pkg/machinery/nethelpers/ /pkg/machinery/nethelpers/
|
||||
|
||||
|
20
go.mod
20
go.mod
@ -44,27 +44,39 @@ require (
|
||||
github.com/gizak/termui/v3 v3.1.0
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/hashicorp/go-getter v1.5.3
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210528123148-fb4eaaa00ad2
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210531051304-b34cb89a106b
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13
|
||||
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43
|
||||
github.com/mdlayher/genetlink v1.0.0
|
||||
github.com/mdlayher/netlink v1.4.1
|
||||
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d
|
||||
github.com/pelletier/go-toml v1.9.0 // indirect
|
||||
github.com/pin/tftp v2.1.0+incompatible
|
||||
github.com/plunder-app/kube-vip v0.3.5
|
||||
github.com/prometheus/client_golang v1.10.0 // indirect
|
||||
github.com/prometheus/common v0.23.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0
|
||||
github.com/rivo/tview v0.0.0-20210531104647-807e706f86d1
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/ryanuber/columnize v2.1.2+incompatible
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/smira/go-xz v0.0.0-20201019130106-9921ed7a9935
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/viper v1.7.1 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/talos-systems/crypto v0.2.1-0.20210601174604-cd18ef62eb9f
|
||||
github.com/talos-systems/go-blockdevice v0.2.1-0.20210526155905-30c2bc3cb62a
|
||||
@ -79,6 +91,7 @@ require (
|
||||
github.com/talos-systems/net v0.2.1-0.20210212213224-05190541b0fa
|
||||
github.com/talos-systems/talos/pkg/machinery v0.0.0-00010101000000-000000000000
|
||||
github.com/u-root/u-root v7.0.0+incompatible
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
github.com/vmware-tanzu/sonobuoy v0.50.0
|
||||
github.com/vmware/govmomi v0.26.0
|
||||
github.com/vmware/vmw-guestinfo v0.0.0-20200218095840-687661b8bd8e
|
||||
@ -88,14 +101,17 @@ require (
|
||||
go.etcd.io/etcd/etcdutl/v3 v3.5.0-rc.0
|
||||
go.uber.org/zap v1.17.0
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
|
||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210506160403-92e472f520a5
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
gopkg.in/freddierice/go-losetup.v1 v1.0.0-20170407175016-fc9adea44124
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
inet.af/netaddr v0.0.0-20210430201628-1d252cf8125e
|
||||
k8s.io/api v0.21.1
|
||||
@ -105,4 +121,6 @@ require (
|
||||
k8s.io/cri-api v0.21.1
|
||||
k8s.io/kubectl v0.21.1
|
||||
k8s.io/kubelet v0.21.1
|
||||
k8s.io/utils v0.0.0-20210305010621-2afb4311ab10 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.1 // indirect
|
||||
)
|
||||
|
56
go.sum
56
go.sum
@ -73,8 +73,6 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
@ -119,7 +117,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alessio/shellescape v1.2.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
@ -130,8 +127,6 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloD
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
||||
github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
@ -197,8 +192,6 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
|
||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.6.0 h1:hOQqNhQdMIi0zmjii4jKUnI0i+NB7ApvTXs2MstI5S0=
|
||||
github.com/cilium/ebpf v0.6.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@ -351,7 +344,6 @@ github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||
@ -406,13 +398,11 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.1.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/firecracker-microvm/firecracker-go-sdk v0.22.0 h1:hk28AO5ArAX9iHomi6axNLK+6+8gz1wi3ooNsUTlSFQ=
|
||||
@ -640,8 +630,6 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
|
||||
@ -700,27 +688,21 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-getter v1.5.3 h1:NF5+zOlQegim+w/EUhSLh6QhXHmZMEeHLQzllkQ3ROU=
|
||||
github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI=
|
||||
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.16.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-memdb v1.3.2 h1:RBKHOsnSszpU6vxq80LzC2BaQjuuvoyaQbkLTf7V7g8=
|
||||
github.com/hashicorp/go-memdb v1.3.2/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
@ -742,7 +724,6 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/raft v1.3.1/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
@ -762,7 +743,6 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210528123148-fb4eaaa00ad2 h1:WDOgJoE6rb7G6A7i1/Yyh5FJeydXeUrXHMRYJo7iFak=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210528123148-fb4eaaa00ad2/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
@ -773,7 +753,6 @@ github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9q
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
@ -800,8 +779,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/k-sone/critbitgo v1.4.0/go.mod h1:7E6pyoyADnFxlUBEKcnfS49b7SUAQGMK+OAp/UQvo0s=
|
||||
github.com/kamhlos/upnp v0.0.0-20210324072331-5661950dff08/go.mod h1:0L/S1RSG4wA4M2Vhau3z7VsYMLxFnsX0bzzgwYRIdYU=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
@ -856,13 +833,11 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
@ -885,8 +860,6 @@ github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JA
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567 h1:x+xs91ZJ+lr0C6sedWeREvck4uGCt+AA1kKXwsHB6jI=
|
||||
github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567/go.mod h1:32w/5dDZWVSEOxyniAgKK4d7dHTuO6TCxWmUznQe3f8=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
@ -1026,11 +999,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/osrg/gobgp v2.0.0+incompatible/go.mod h1:vGVJPLW6JFDD7WA1vJsjB8OKmbbC2TKwHtr90CZS/u4=
|
||||
github.com/packethost/packngo v0.13.0/go.mod h1:YrtUNN9IRjjqN6zK+cy2IYoi3EjHfoWTWxJkI1I1Vk0=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
@ -1051,21 +1021,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/plunder-app/kube-vip v0.3.5 h1:+i4TLCWbFNNj8RHfF6BHAReOI0NCxxUAGhX4/ZdlA34=
|
||||
github.com/plunder-app/kube-vip v0.3.5/go.mod h1:lN0PiOK6uwByLTLLSM45En/VOycZz3/8wQ/UQl7Qu3E=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
|
||||
@ -1080,7 +1046,6 @@ github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
@ -1093,7 +1058,6 @@ github.com/prometheus/common v0.23.0 h1:GXWvPYuTUenIa+BhOq/x+L/QZzCqASkVRny5KTlP
|
||||
github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q=
|
||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
@ -1122,8 +1086,6 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5 h1:/kzTBQ20DbbhSNaBXiFEk2gPrGhY26kajwC1ro/Vlh8=
|
||||
github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5/go.mod h1:FwstIpm6vX98QgtR8KEwZcVjiRn2WP76LjXAHj84fK0=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
@ -1256,7 +1218,6 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
||||
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
@ -1276,7 +1237,6 @@ github.com/viniciuschiele/tarx v0.0.0-20151205142357-6e3da540444d h1:Z8Bp/K+wf1+
|
||||
github.com/viniciuschiele/tarx v0.0.0-20151205142357-6e3da540444d/go.mod h1:8uo3DXfN526YN7JjAp4JkOMm4foTW4vPzPHaAzb4xiY=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20200221165523-c79a4b7b4066/go.mod h1:FSQhuTO7eHT34mPzX+B04SUAjiqLxtXs1et0S6l9k4k=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
@ -1312,8 +1272,6 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f h1:Wku8eEdeJqIOFHtrfkYUByc4bCaTeA6fL0UJgfEiFMI=
|
||||
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
@ -1414,7 +1372,6 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@ -1518,7 +1475,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210505214959-0714010a04ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
@ -1596,7 +1552,6 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -1610,7 +1565,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602100848-8d3cce7afc34/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -1622,7 +1576,6 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200928205150-006507a75852/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -1694,7 +1647,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
@ -1821,7 +1773,6 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210505142820-a42aa055cf76/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210524171403-669157292da3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
@ -1925,15 +1876,12 @@ k8s.io/api v0.18.5/go.mod h1:tN+e/2nbdGKOAH55NMV8oGrMG+3uRlA9GaRfvnCCSNk=
|
||||
k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
|
||||
k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
|
||||
k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
|
||||
k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
|
||||
k8s.io/api v0.21.1 h1:94bbZ5NTjdINJEdzOkpS4vdPhkb1VFpTYC9zh43f75c=
|
||||
k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s=
|
||||
k8s.io/apimachinery v0.18.5/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||
k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
|
||||
k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
|
||||
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
|
||||
k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
|
||||
k8s.io/apimachinery v0.21.1 h1:Q6XuHGlj2xc+hlMCvqyYfbv3H7SRGn2c8NycxJquDVs=
|
||||
k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
|
||||
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
|
||||
@ -1947,7 +1895,6 @@ k8s.io/client-go v0.18.5/go.mod h1:EsiD+7Fx+bRckKWZXnAXRKKetm1WuzPagH4iOSC8x58=
|
||||
k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y=
|
||||
k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
|
||||
k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
|
||||
k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
|
||||
k8s.io/client-go v0.21.1 h1:bhblWYLZKUu+pm50plvQF8WpY6TXdRRtcS/K9WauOj4=
|
||||
k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs=
|
||||
k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q=
|
||||
@ -1976,7 +1923,6 @@ k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts=
|
||||
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
|
||||
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
|
||||
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0=
|
||||
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
|
||||
@ -1995,7 +1941,6 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
|
||||
sigs.k8s.io/kind v0.10.0/go.mod h1:fb32zUw7ewC47bPwLnwhf47wd/vADtv3c38KP7sjIlo=
|
||||
sigs.k8s.io/kustomize/api v0.8.8 h1:G2z6JPSSjtWWgMeWSoHdXqyftJNmMmyxXpwENGoOtGE=
|
||||
sigs.k8s.io/kustomize/api v0.8.8/go.mod h1:He1zoK0nk43Pc6NlV085xDXDXTNprtcyKZVm3swsdNY=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIMgYrEDfwOeFdMK0=
|
||||
@ -2004,7 +1949,6 @@ sigs.k8s.io/kustomize/kyaml v0.10.17 h1:4zrV0ym5AYa0e512q7K3Wp1u7mzoWW0xR3UHJcGW
|
||||
sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
|
@ -46,7 +46,7 @@ Added the flag `cluster.coreDNS.disabled` to coreDNS deployment during the clust
|
||||
title = "Default to Bootstrap workflow"
|
||||
description = """\
|
||||
The `init.yaml` is no longer an output of `talosctl gen config`.
|
||||
We now encourage using the bootstrap API, instead it `init` node types, as we
|
||||
We now encourage using the bootstrap API, instead of `init` node types, as we
|
||||
intend on deprecating this machine type in the future.
|
||||
The `init.yaml` and `controlplane.yaml` machine configs are identical with the
|
||||
exception of the machine type.
|
||||
|
@ -137,6 +137,17 @@ func (suite *EtcFileSuite) TestFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *EtcFileSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), files.NewEtcFileSpec(files.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestEtcFileSuite(t *testing.T) {
|
||||
suite.Run(t, new(EtcFileSuite))
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
|
||||
"github.com/talos-systems/talos/pkg/resources/config"
|
||||
"github.com/talos-systems/talos/pkg/resources/k8s"
|
||||
"github.com/talos-systems/talos/pkg/resources/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
)
|
||||
|
||||
// ExtraManifestController renders manifests based on templates and config/secrets.
|
||||
@ -43,9 +43,9 @@ func (ctrl *ExtraManifestController) Inputs() []controller.Input {
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: v1alpha1.NamespaceName,
|
||||
Type: v1alpha1.ServiceType,
|
||||
ID: pointer.ToString("networkd"),
|
||||
Namespace: network.NamespaceName,
|
||||
Type: network.StatusType,
|
||||
ID: pointer.ToString(network.StatusID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}
|
||||
@ -72,8 +72,8 @@ func (ctrl *ExtraManifestController) Run(ctx context.Context, r controller.Runti
|
||||
case <-r.EventCh():
|
||||
}
|
||||
|
||||
// wait for networkd to be healthy as networking is required to download extra manifests
|
||||
networkdResource, err := r.Get(ctx, resource.NewMetadata(v1alpha1.NamespaceName, v1alpha1.ServiceType, "networkd", resource.VersionUndefined))
|
||||
// wait for network to be ready as networking is required to download extra manifests
|
||||
networkResource, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.StatusType, network.StatusID, resource.VersionUndefined))
|
||||
if err != nil {
|
||||
if state.IsNotFoundError(err) {
|
||||
continue
|
||||
@ -82,7 +82,9 @@ func (ctrl *ExtraManifestController) Run(ctx context.Context, r controller.Runti
|
||||
return err
|
||||
}
|
||||
|
||||
if !networkdResource.(*v1alpha1.Service).Healthy() {
|
||||
networkStatus := networkResource.(*network.Status).TypedSpec()
|
||||
|
||||
if !(networkStatus.AddressReady && networkStatus.ConnectivityReady) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/logging"
|
||||
"github.com/talos-systems/talos/pkg/resources/config"
|
||||
"github.com/talos-systems/talos/pkg/resources/k8s"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
"github.com/talos-systems/talos/pkg/resources/v1alpha1"
|
||||
)
|
||||
|
||||
@ -108,12 +109,12 @@ metadata:
|
||||
},
|
||||
})
|
||||
|
||||
serviceNetworkd := v1alpha1.NewService("networkd")
|
||||
serviceNetworkd.SetRunning(true)
|
||||
serviceNetworkd.SetHealthy(true)
|
||||
statusNetwork := network.NewStatus(network.NamespaceName, network.StatusID)
|
||||
statusNetwork.TypedSpec().AddressReady = true
|
||||
statusNetwork.TypedSpec().ConnectivityReady = true
|
||||
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, configExtraManifests))
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, serviceNetworkd))
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, statusNetwork))
|
||||
|
||||
suite.Assert().NoError(retry.Constant(10*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
talosconfig "github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/resources/config"
|
||||
@ -24,7 +25,8 @@ import (
|
||||
|
||||
// AddressConfigController manages network.AddressSpec based on machine configuration, kernel cmdline and some built-in defaults.
|
||||
type AddressConfigController struct {
|
||||
Cmdline *procfs.Cmdline
|
||||
Cmdline *procfs.Cmdline
|
||||
V1Alpha1Mode runtime.Mode
|
||||
}
|
||||
|
||||
// Name implements controller.Controller interface.
|
||||
@ -179,6 +181,11 @@ func (ctrl *AddressConfigController) apply(ctx context.Context, r controller.Run
|
||||
}
|
||||
|
||||
func (ctrl *AddressConfigController) loopbackDefaults() []network.AddressSpecSpec {
|
||||
if ctrl.V1Alpha1Mode == runtime.ModeContainer {
|
||||
// skip configuring lo addresses in container mode
|
||||
return nil
|
||||
}
|
||||
|
||||
return []network.AddressSpecSpec{
|
||||
{
|
||||
Address: netaddr.IPPrefix{
|
||||
|
@ -235,6 +235,25 @@ func (suite *AddressConfigSuite) TestMachineConfiguration() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *AddressConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func TestAddressConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(AddressConfigSuite))
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package network provides controllers which manage network resources.
|
||||
//
|
||||
//nolint:dupl
|
||||
package network
|
||||
|
||||
import (
|
||||
|
@ -190,6 +190,17 @@ func (suite *AddressMergeSuite) TestMerge() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *AddressMergeSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewAddressSpec(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestAddressMergeSuite(t *testing.T) {
|
||||
suite.Run(t, new(AddressMergeSuite))
|
||||
}
|
||||
|
@ -6,8 +6,10 @@ package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
@ -48,7 +50,7 @@ func (ctrl *AddressSpecController) Outputs() []controller.Output {
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo,dupl
|
||||
//nolint:gocyclo
|
||||
func (ctrl *AddressSpecController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
// watch link changes as some address might need to be re-applied if the link appears
|
||||
watcher, err := watch.NewRtNetlink(r, unix.RTMGRP_LINK)
|
||||
@ -219,7 +221,10 @@ func (ctrl *AddressSpecController) syncAddress(ctx context.Context, r controller
|
||||
Flags: uint32(address.TypedSpec().Flags),
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error adding address %s to %q: %w", address.TypedSpec().Address, address.TypedSpec().LinkName, err)
|
||||
// ignore EEXIST error
|
||||
if !errors.Is(err, os.ErrExist) {
|
||||
return fmt.Errorf("error adding address %s to %q: %w", address.TypedSpec().Address, address.TypedSpec().LinkName, err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("assigned address", zap.Stringer("address", address.TypedSpec().Address), zap.String("link", address.TypedSpec().LinkName))
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -58,6 +59,10 @@ func (suite *AddressSpecSuite) SetupTest() {
|
||||
suite.startRuntime()
|
||||
}
|
||||
|
||||
func (suite *AddressSpecSuite) uniqueDummyInterface() string {
|
||||
return fmt.Sprintf("dummy%02x%02x%02x", rand.Int31()&0xff, rand.Int31()&0xff, rand.Int31()&0xff)
|
||||
}
|
||||
|
||||
func (suite *AddressSpecSuite) startRuntime() {
|
||||
suite.wg.Add(1)
|
||||
|
||||
@ -163,7 +168,7 @@ func (suite *AddressSpecSuite) TestLoopback() {
|
||||
}
|
||||
|
||||
func (suite *AddressSpecSuite) TestDummy() {
|
||||
const dummyInterface = "dummy9"
|
||||
dummyInterface := suite.uniqueDummyInterface()
|
||||
|
||||
conn, err := rtnetlink.Dial(nil)
|
||||
suite.Require().NoError(err)
|
||||
@ -223,6 +228,17 @@ func (suite *AddressSpecSuite) TestDummy() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *AddressSpecSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewAddressSpec(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestAddressSpecSuite(t *testing.T) {
|
||||
suite.Run(t, new(AddressSpecSuite))
|
||||
}
|
||||
|
@ -104,6 +104,14 @@ func (suite *AddressStatusSuite) TestLoopback() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *AddressStatusSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
}
|
||||
|
||||
func TestAddressStatusSuite(t *testing.T) {
|
||||
suite.Run(t, new(AddressStatusSuite))
|
||||
}
|
||||
|
@ -240,6 +240,29 @@ func (suite *EtcFileConfigSuite) TestOnlyHostname() {
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *EtcFileConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewHostnameStatus(network.NamespaceName, "bar")))
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewResolverStatus(network.NamespaceName, "bar")))
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewNodeAddress(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestEtcFileConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(EtcFileConfigSuite))
|
||||
}
|
||||
|
@ -209,6 +209,27 @@ func (suite *HostnameConfigSuite) TestMachineConfiguration() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *HostnameConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewNodeAddress(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestHostnameConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(HostnameConfigSuite))
|
||||
}
|
||||
|
@ -132,6 +132,8 @@ func (suite *HostnameMergeSuite) TestMerge() {
|
||||
"hostname",
|
||||
}, func(r *network.HostnameSpec) error {
|
||||
suite.Assert().Equal("bar.com", r.TypedSpec().FQDN())
|
||||
suite.Assert().Equal("bar", r.TypedSpec().Hostname)
|
||||
suite.Assert().Equal("com", r.TypedSpec().Domainname)
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -153,6 +155,17 @@ func (suite *HostnameMergeSuite) TestMerge() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *HostnameMergeSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewHostnameSpec(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestHostnameMergeSuite(t *testing.T) {
|
||||
suite.Run(t, new(HostnameMergeSuite))
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ func (ctrl *HostnameSpecController) Run(ctx context.Context, r controller.Runtim
|
||||
|
||||
// apply hostname unless running in container mode
|
||||
if ctrl.V1Alpha1Mode != v1alpha1runtime.ModeContainer {
|
||||
logger.Info("setting hostname", zap.String("hostname", spec.TypedSpec().Hostname), zap.String("domainname", spec.TypedSpec().Domainname))
|
||||
|
||||
if err = unix.Sethostname([]byte(spec.TypedSpec().Hostname)); err != nil {
|
||||
return fmt.Errorf("error setting hostname: %w", err)
|
||||
}
|
||||
|
@ -101,6 +101,17 @@ func (suite *HostnameSpecSuite) TestSpec() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *HostnameSpecSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewHostnameSpec(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestHostnameSpecSuite(t *testing.T) {
|
||||
suite.Run(t, new(HostnameSpecSuite))
|
||||
}
|
||||
|
@ -382,6 +382,27 @@ func (suite *LinkConfigSuite) TestDefaultUp() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *LinkConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkStatus(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestLinkConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(LinkConfigSuite))
|
||||
}
|
||||
|
@ -185,6 +185,17 @@ func (suite *LinkMergeSuite) TestMerge() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *LinkMergeSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkSpec(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestLinkMergeSuite(t *testing.T) {
|
||||
suite.Run(t, new(LinkMergeSuite))
|
||||
}
|
||||
|
@ -554,6 +554,17 @@ func (suite *LinkSpecSuite) TestWireguard() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *LinkSpecSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkSpec(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestLinkSpecSuite(t *testing.T) {
|
||||
suite.Run(t, new(LinkSpecSuite))
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -67,6 +68,10 @@ func (suite *LinkStatusSuite) startRuntime() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (suite *LinkStatusSuite) uniqueDummyInterface() string {
|
||||
return fmt.Sprintf("dummy%02x%02x%02x", rand.Int31()&0xff, rand.Int31()&0xff, rand.Int31()&0xff)
|
||||
}
|
||||
|
||||
func (suite *LinkStatusSuite) assertInterfaces(requiredIDs []string, check func(*network.LinkStatus) error) error {
|
||||
missingIDs := make(map[string]struct{}, len(requiredIDs))
|
||||
|
||||
@ -127,7 +132,7 @@ func (suite *LinkStatusSuite) TestLoopbackInterface() {
|
||||
}
|
||||
|
||||
func (suite *LinkStatusSuite) TestDummyInterface() {
|
||||
const dummyInterface = "dummy9"
|
||||
dummyInterface := suite.uniqueDummyInterface()
|
||||
|
||||
conn, err := rtnetlink.Dial(nil)
|
||||
suite.Require().NoError(err)
|
||||
@ -187,6 +192,17 @@ func (suite *LinkStatusSuite) TestDummyInterface() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *LinkStatusSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkRefresh(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestLinkStatusSuite(t *testing.T) {
|
||||
suite.Run(t, new(LinkStatusSuite))
|
||||
}
|
||||
|
@ -125,6 +125,18 @@ func (suite *NodeAddressSuite) TestDefaults() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *NodeAddressSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewAddressStatus(network.NamespaceName, "bar")))
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkStatus(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestNodeAddressSuite(t *testing.T) {
|
||||
suite.Run(t, new(NodeAddressSuite))
|
||||
}
|
||||
|
@ -148,6 +148,7 @@ func (d *DHCP4) TimeServerSpecs() []network.TimeServerSpecSpec {
|
||||
return d.timeservers
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (d *DHCP4) parseAck(ack *dhcpv4.DHCPv4) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
@ -194,6 +195,7 @@ func (d *DHCP4) parseAck(ack *dhcpv4.DHCPv4) {
|
||||
d.routes = append(d.routes, network.RouteSpecSpec{
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Destination: dst,
|
||||
Source: addr,
|
||||
Gateway: gw,
|
||||
OutLinkName: d.linkName,
|
||||
Table: nethelpers.TableMain,
|
||||
@ -222,6 +224,10 @@ func (d *DHCP4) parseAck(ack *dhcpv4.DHCPv4) {
|
||||
}
|
||||
}
|
||||
|
||||
for i := range d.routes {
|
||||
d.routes[i].Normalize()
|
||||
}
|
||||
|
||||
if len(ack.DNS()) > 0 {
|
||||
dns := make([]netaddr.IP, len(ack.DNS()))
|
||||
|
||||
@ -240,12 +246,20 @@ func (d *DHCP4) parseAck(ack *dhcpv4.DHCPv4) {
|
||||
}
|
||||
|
||||
if ack.HostName() != "" {
|
||||
d.hostname = []network.HostnameSpecSpec{
|
||||
{
|
||||
Hostname: ack.HostName(),
|
||||
Domainname: ack.DomainName(),
|
||||
ConfigLayer: network.ConfigOperator,
|
||||
},
|
||||
spec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigOperator,
|
||||
}
|
||||
|
||||
if err = spec.ParseFQDN(ack.HostName()); err == nil {
|
||||
if ack.DomainName() != "" {
|
||||
spec.Domainname = ack.DomainName()
|
||||
}
|
||||
|
||||
d.hostname = []network.HostnameSpecSpec{
|
||||
spec,
|
||||
}
|
||||
} else {
|
||||
d.hostname = nil
|
||||
}
|
||||
} else {
|
||||
d.hostname = nil
|
||||
|
@ -456,6 +456,27 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationVIP() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *OperatorConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkStatus(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestOperatorConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(OperatorConfigSuite))
|
||||
}
|
||||
|
@ -0,0 +1,203 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//nolint:dupl
|
||||
package network_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller/runtime"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
|
||||
netctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network"
|
||||
v1alpha1runtime "github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/pkg/logging"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
)
|
||||
|
||||
type PlatformConfigSuite struct {
|
||||
suite.Suite
|
||||
|
||||
state state.State
|
||||
|
||||
runtime *runtime.Runtime
|
||||
wg sync.WaitGroup
|
||||
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) SetupTest() {
|
||||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
|
||||
suite.state = state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
|
||||
var err error
|
||||
|
||||
suite.runtime, err = runtime.NewRuntime(suite.state, logging.Wrap(log.Writer()))
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) startRuntime() {
|
||||
suite.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer suite.wg.Done()
|
||||
|
||||
suite.Assert().NoError(suite.runtime.Run(suite.ctx))
|
||||
}()
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) assertHostnames(requiredIDs []string, check func(*network.HostnameSpec) error) error {
|
||||
missingIDs := make(map[string]struct{}, len(requiredIDs))
|
||||
|
||||
for _, id := range requiredIDs {
|
||||
missingIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.ConfigNamespaceName, network.HostnameSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
_, required := missingIDs[res.Metadata().ID()]
|
||||
if !required {
|
||||
continue
|
||||
}
|
||||
|
||||
delete(missingIDs, res.Metadata().ID())
|
||||
|
||||
if err = check(res.(*network.HostnameSpec)); err != nil {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingIDs) > 0 {
|
||||
return retry.ExpectedError(fmt.Errorf("some resources are missing: %q", missingIDs))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) assertNoHostname(id string) error {
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.ConfigNamespaceName, network.HostnameSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
if res.Metadata().ID() == id {
|
||||
return retry.ExpectedError(fmt.Errorf("spec %q is still there", id))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestNoPlatform() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoHostname("platform/hostname")
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMock() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{hostname: []byte("talos-e2e-897b4e49-gcp-controlplane-jvcnl.c.talos-testbed.internal")},
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertHostnames([]string{
|
||||
"platform/hostname",
|
||||
}, func(r *network.HostnameSpec) error {
|
||||
suite.Assert().Equal("talos-e2e-897b4e49-gcp-controlplane-jvcnl", r.TypedSpec().Hostname)
|
||||
suite.Assert().Equal("c.talos-testbed.internal", r.TypedSpec().Domainname)
|
||||
suite.Assert().Equal(network.ConfigPlatform, r.TypedSpec().ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockNoDomain() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{hostname: []byte("talos-e2e-897b4e49-gcp-controlplane-jvcnl")},
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertHostnames([]string{
|
||||
"platform/hostname",
|
||||
}, func(r *network.HostnameSpec) error {
|
||||
suite.Assert().Equal("talos-e2e-897b4e49-gcp-controlplane-jvcnl", r.TypedSpec().Hostname)
|
||||
suite.Assert().Equal("", r.TypedSpec().Domainname)
|
||||
suite.Assert().Equal(network.ConfigPlatform, r.TypedSpec().ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
}
|
||||
|
||||
func TestPlatformConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(PlatformConfigSuite))
|
||||
}
|
||||
|
||||
type platformMock struct {
|
||||
hostname []byte
|
||||
}
|
||||
|
||||
func (mock *platformMock) Name() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
func (mock *platformMock) Configuration(context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mock *platformMock) Hostname(context.Context) ([]byte, error) {
|
||||
return mock.hostname, nil
|
||||
}
|
||||
|
||||
func (mock *platformMock) Mode() v1alpha1runtime.Mode {
|
||||
return v1alpha1runtime.ModeCloud
|
||||
}
|
||||
|
||||
func (mock *platformMock) ExternalIPs(context.Context) ([]net.IP, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mock *platformMock) KernelArgs() procfs.Parameters {
|
||||
return nil
|
||||
}
|
@ -199,6 +199,25 @@ func (suite *ResolverConfigSuite) TestMachineConfiguration() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *ResolverConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func TestResolverConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ResolverConfigSuite))
|
||||
}
|
||||
|
@ -154,6 +154,17 @@ func (suite *ResolverMergeSuite) TestMerge() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *ResolverMergeSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewResolverSpec(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestResolverMergeSuite(t *testing.T) {
|
||||
suite.Run(t, new(ResolverMergeSuite))
|
||||
}
|
||||
|
@ -101,6 +101,17 @@ func (suite *ResolverSpecSuite) TestSpec() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *ResolverSpecSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewResolverSpec(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestResolverSpecSuite(t *testing.T) {
|
||||
suite.Run(t, new(ResolverSpecSuite))
|
||||
}
|
||||
|
@ -200,15 +200,12 @@ func (ctrl *RouteConfigController) parseCmdline(logger *zap.Logger) (route netwo
|
||||
route.OutLinkName = settings.LinkName
|
||||
route.ConfigLayer = network.ConfigCmdline
|
||||
|
||||
route.Normalize()
|
||||
|
||||
return route
|
||||
}
|
||||
|
||||
var (
|
||||
zero16 = netaddr.MustParseIP("::")
|
||||
zero4 = netaddr.MustParseIP("0.0.0.0")
|
||||
)
|
||||
|
||||
//nolint:gocyclo,cyclop
|
||||
//nolint:gocyclo
|
||||
func (ctrl *RouteConfigController) parseMachineConfiguration(logger *zap.Logger, cfgProvider talosconfig.Provider) (routes []network.RouteSpecSpec) {
|
||||
convert := func(linkName string, in talosconfig.Route) (route network.RouteSpecSpec, err error) {
|
||||
if in.Network() != "" {
|
||||
@ -216,11 +213,6 @@ func (ctrl *RouteConfigController) parseMachineConfiguration(logger *zap.Logger,
|
||||
if err != nil {
|
||||
return route, fmt.Errorf("error parsing route network: %w", err)
|
||||
}
|
||||
|
||||
if route.Destination.Bits == 0 && (route.Destination.IP.Compare(zero4) == 0 || route.Destination.IP.Compare(zero16) == 0) {
|
||||
// clear destination to be zero value to support "0.0.0.0/0" routes
|
||||
route.Destination = netaddr.IPPrefix{}
|
||||
}
|
||||
}
|
||||
|
||||
route.Gateway, err = netaddr.ParseIP(in.Gateway())
|
||||
@ -228,6 +220,8 @@ func (ctrl *RouteConfigController) parseMachineConfiguration(logger *zap.Logger,
|
||||
return route, fmt.Errorf("error parsing route gateway: %w", err)
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
route.Priority = in.Metric()
|
||||
if route.Priority == 0 {
|
||||
route.Priority = DefaultRouteMetric
|
||||
@ -244,15 +238,6 @@ func (ctrl *RouteConfigController) parseMachineConfiguration(logger *zap.Logger,
|
||||
route.OutLinkName = linkName
|
||||
route.ConfigLayer = network.ConfigMachineConfiguration
|
||||
|
||||
switch {
|
||||
case route.Destination.IP.IsLinkLocalUnicast() || route.Destination.IP.IsLinkLocalMulticast():
|
||||
route.Scope = nethelpers.ScopeLink
|
||||
case route.Destination.IP.IsLoopback():
|
||||
route.Scope = nethelpers.ScopeHost
|
||||
default:
|
||||
route.Scope = nethelpers.ScopeGlobal
|
||||
}
|
||||
|
||||
route.Type = nethelpers.TypeUnicast
|
||||
|
||||
if route.Destination.IP.IsMulticast() {
|
||||
|
@ -106,7 +106,7 @@ func (suite *RouteConfigSuite) TestCmdline() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertRoutes([]string{
|
||||
"cmdline//172.20.0.1",
|
||||
"cmdline/172.20.0.1/",
|
||||
}, func(r *network.RouteSpec) error {
|
||||
suite.Assert().Equal("eth1", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(network.ConfigCmdline, r.TypedSpec().ConfigLayer)
|
||||
@ -195,20 +195,20 @@ func (suite *RouteConfigSuite) TestMachineConfiguration() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertRoutes([]string{
|
||||
"configuration//2001:470:6d:30e:8ed2:b60c:9d2f:803b",
|
||||
"configuration/10.0.3.0/24/10.0.3.1",
|
||||
"configuration/192.168.0.0/18/192.168.0.25",
|
||||
"configuration/2001:470:6d:30e:8ed2:b60c:9d2f:803b/",
|
||||
"configuration/10.0.3.1/10.0.3.0/24",
|
||||
"configuration/192.168.0.25/192.168.0.0/18",
|
||||
}, func(r *network.RouteSpec) error {
|
||||
switch r.Metadata().ID() {
|
||||
case "configuration//2001:470:6d:30e:8ed2:b60c:9d2f:803b":
|
||||
case "configuration/2001:470:6d:30e:8ed2:b60c:9d2f:803b/":
|
||||
suite.Assert().Equal("eth2", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(nethelpers.FamilyInet6, r.TypedSpec().Family)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().Priority)
|
||||
case "configuration/10.0.3.0/24/10.0.3.1":
|
||||
case "configuration/10.0.3.1/10.0.3.0/24":
|
||||
suite.Assert().Equal("eth0.24", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, r.TypedSpec().Family)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().Priority)
|
||||
case "configuration/192.168.0.0/18/192.168.0.25":
|
||||
case "configuration/192.168.0.25/192.168.0.0/18":
|
||||
suite.Assert().Equal("eth3", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, r.TypedSpec().Family)
|
||||
suite.Assert().EqualValues(25, r.TypedSpec().Priority)
|
||||
@ -221,6 +221,25 @@ func (suite *RouteConfigSuite) TestMachineConfiguration() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RouteConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func TestRouteConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(RouteConfigSuite))
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package network provides controllers which manage network resources.
|
||||
//
|
||||
//nolint:dupl
|
||||
package network
|
||||
|
||||
import (
|
||||
|
@ -154,13 +154,13 @@ func (suite *RouteMergeSuite) TestMerge() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertRoutes([]string{
|
||||
"/10.5.0.3",
|
||||
"10.0.0.35/32/10.0.0.34",
|
||||
"10.5.0.3/",
|
||||
"10.0.0.34/10.0.0.35/32",
|
||||
}, func(r *network.RouteSpec) error {
|
||||
switch r.Metadata().ID() {
|
||||
case "/10.5.0.3":
|
||||
case "10.5.0.3/":
|
||||
suite.Assert().Equal(*dhcp.TypedSpec(), *r.TypedSpec())
|
||||
case "10.0.0.35/32/10.0.0.34":
|
||||
case "10.0.0.34/10.0.0.35/32":
|
||||
suite.Assert().Equal(*static.TypedSpec(), *r.TypedSpec())
|
||||
}
|
||||
|
||||
@ -173,16 +173,16 @@ func (suite *RouteMergeSuite) TestMerge() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertRoutes([]string{
|
||||
"/10.5.0.3",
|
||||
"10.0.0.35/32/10.0.0.34",
|
||||
"10.5.0.3/",
|
||||
"10.0.0.34/10.0.0.35/32",
|
||||
}, func(r *network.RouteSpec) error {
|
||||
switch r.Metadata().ID() {
|
||||
case "/10.5.0.3":
|
||||
case "10.5.0.3/":
|
||||
if *cmdline.TypedSpec() != *r.TypedSpec() {
|
||||
// using retry here, as it might not be reconciled immediately
|
||||
return retry.ExpectedError(fmt.Errorf("not equal yet"))
|
||||
}
|
||||
case "10.0.0.35/32/10.0.0.34":
|
||||
case "10.0.0.34/10.0.0.35/32":
|
||||
suite.Assert().Equal(*static.TypedSpec(), *r.TypedSpec())
|
||||
}
|
||||
|
||||
@ -194,10 +194,21 @@ func (suite *RouteMergeSuite) TestMerge() {
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoRoute("10.0.0.35/32/10.0.0.34")
|
||||
return suite.assertNoRoute("10.0.0.34/10.0.0.35/32")
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RouteMergeSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewRouteSpec(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestRouteMergeSuite(t *testing.T) {
|
||||
suite.Run(t, new(RouteMergeSuite))
|
||||
}
|
||||
|
@ -10,12 +10,14 @@ import (
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/sys/unix"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network/watch"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
)
|
||||
|
||||
@ -45,10 +47,10 @@ func (ctrl *RouteSpecController) Outputs() []controller.Output {
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo,dupl
|
||||
//nolint:gocyclo
|
||||
func (ctrl *RouteSpecController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
// watch link changes as some routes might need to be re-applied if the link appears
|
||||
watcher, err := watch.NewRtNetlink(r, unix.RTMGRP_LINK)
|
||||
watcher, err := watch.NewRtNetlink(r, unix.RTMGRP_LINK|unix.RTMGRP_IPV4_ROUTE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -98,18 +100,24 @@ func (ctrl *RouteSpecController) Run(ctx context.Context, r controller.Runtime,
|
||||
return fmt.Errorf("error listing addresses: %w", err)
|
||||
}
|
||||
|
||||
// loop over route and make reconcile decision
|
||||
var multiErr *multierror.Error
|
||||
|
||||
// loop over routes and make reconcile decision
|
||||
for _, res := range list.Items {
|
||||
route := res.(*network.RouteSpec) //nolint:forcetypeassert,errcheck
|
||||
|
||||
if err = ctrl.syncRoute(ctx, r, logger, conn, links, routes, route); err != nil {
|
||||
return err
|
||||
multiErr = multierror.Append(multiErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = multiErr.ErrorOrNil(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findRoutes(routes []rtnetlink.RouteMessage, destination netaddr.IPPrefix, gateway netaddr.IP) []*rtnetlink.RouteMessage {
|
||||
func findRoutes(routes []rtnetlink.RouteMessage, destination netaddr.IPPrefix, gateway netaddr.IP, table nethelpers.RoutingTable) []*rtnetlink.RouteMessage {
|
||||
var result []*rtnetlink.RouteMessage //nolint:prealloc
|
||||
|
||||
for i, route := range routes {
|
||||
@ -125,13 +133,17 @@ func findRoutes(routes []rtnetlink.RouteMessage, destination netaddr.IPPrefix, g
|
||||
continue
|
||||
}
|
||||
|
||||
if nethelpers.RoutingTable(route.Table) != table {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, &routes[i])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
//nolint:gocyclo,cyclop
|
||||
func (ctrl *RouteSpecController) syncRoute(ctx context.Context, r controller.Runtime, logger *zap.Logger, conn *rtnetlink.Conn,
|
||||
links []rtnetlink.LinkMessage, routes []rtnetlink.RouteMessage, route *network.RouteSpec) error {
|
||||
linkIndex := resolveLinkName(links, route.TypedSpec().OutLinkName)
|
||||
@ -144,7 +156,7 @@ func (ctrl *RouteSpecController) syncRoute(ctx context.Context, r controller.Run
|
||||
|
||||
switch route.Metadata().Phase() {
|
||||
case resource.PhaseTearingDown:
|
||||
for _, existing := range findRoutes(routes, route.TypedSpec().Destination, route.TypedSpec().Gateway) {
|
||||
for _, existing := range findRoutes(routes, route.TypedSpec().Destination, route.TypedSpec().Gateway, route.TypedSpec().Table) {
|
||||
// delete route
|
||||
if err := conn.Route.Delete(existing); err != nil {
|
||||
return fmt.Errorf("error removing route: %w", err)
|
||||
@ -165,15 +177,17 @@ func (ctrl *RouteSpecController) syncRoute(ctx context.Context, r controller.Run
|
||||
|
||||
matchFound := false
|
||||
|
||||
for _, existing := range findRoutes(routes, route.TypedSpec().Destination, route.TypedSpec().Gateway) {
|
||||
for _, existing := range findRoutes(routes, route.TypedSpec().Destination, route.TypedSpec().Gateway, route.TypedSpec().Table) {
|
||||
// check if existing matches the spec: if it does, skip update
|
||||
if existing.Scope == uint8(route.TypedSpec().Scope) && existing.Flags == uint32(route.TypedSpec().Flags) &&
|
||||
existing.Protocol == uint8(route.TypedSpec().Protocol) && existing.Flags == uint32(route.TypedSpec().Flags) &&
|
||||
existing.Attributes.OutIface == linkIndex && existing.Attributes.Priority == route.TypedSpec().Priority &&
|
||||
existing.Attributes.Table == uint32(route.TypedSpec().Table) {
|
||||
existing.Attributes.Table == uint32(route.TypedSpec().Table) &&
|
||||
(route.TypedSpec().Source.IsZero() ||
|
||||
existing.Attributes.Src.Equal(route.TypedSpec().Source.IP.IPAddr().IP)) {
|
||||
matchFound = true
|
||||
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
// delete route, it doesn't match the spec
|
||||
@ -192,12 +206,14 @@ func (ctrl *RouteSpecController) syncRoute(ctx context.Context, r controller.Run
|
||||
msg := &rtnetlink.RouteMessage{
|
||||
Family: uint8(route.TypedSpec().Family),
|
||||
DstLength: route.TypedSpec().Destination.Bits,
|
||||
SrcLength: route.TypedSpec().Source.Bits,
|
||||
Protocol: uint8(route.TypedSpec().Protocol),
|
||||
Scope: uint8(route.TypedSpec().Scope),
|
||||
Type: uint8(route.TypedSpec().Type),
|
||||
Flags: uint32(route.TypedSpec().Flags),
|
||||
Attributes: rtnetlink.RouteAttributes{
|
||||
Dst: route.TypedSpec().Destination.IP.IPAddr().IP,
|
||||
Src: route.TypedSpec().Source.IP.IPAddr().IP,
|
||||
Gateway: route.TypedSpec().Gateway.IPAddr().IP,
|
||||
OutIface: linkIndex,
|
||||
Priority: route.TypedSpec().Priority,
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -21,6 +23,7 @@ import (
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
"golang.org/x/sys/unix"
|
||||
"inet.af/netaddr"
|
||||
|
||||
netctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network"
|
||||
@ -56,6 +59,10 @@ func (suite *RouteSpecSuite) SetupTest() {
|
||||
suite.startRuntime()
|
||||
}
|
||||
|
||||
func (suite *RouteSpecSuite) uniqueDummyInterface() string {
|
||||
return fmt.Sprintf("dummy%02x%02x%02x", rand.Int31()&0xff, rand.Int31()&0xff, rand.Int31()&0xff)
|
||||
}
|
||||
|
||||
func (suite *RouteSpecSuite) startRuntime() {
|
||||
suite.wg.Add(1)
|
||||
|
||||
@ -241,6 +248,139 @@ func (suite *RouteSpecSuite) TestDefaultRoute() {
|
||||
suite.Require().NoError(suite.state.Destroy(suite.ctx, def.Metadata()))
|
||||
}
|
||||
|
||||
func (suite *RouteSpecSuite) TestDefaultAndInterfaceRoutes() {
|
||||
dummyInterface := suite.uniqueDummyInterface()
|
||||
|
||||
conn, err := rtnetlink.Dial(nil)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
defer conn.Close() //nolint:errcheck
|
||||
|
||||
suite.Require().NoError(conn.Link.New(&rtnetlink.LinkMessage{
|
||||
Type: unix.ARPHRD_ETHER,
|
||||
Flags: unix.IFF_UP,
|
||||
Change: unix.IFF_UP,
|
||||
Attributes: &rtnetlink.LinkAttributes{
|
||||
Name: dummyInterface,
|
||||
MTU: 1400,
|
||||
Info: &rtnetlink.LinkInfo{
|
||||
Kind: "dummy",
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
iface, err := net.InterfaceByName(dummyInterface)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
defer conn.Link.Delete(uint32(iface.Index)) //nolint:errcheck
|
||||
|
||||
localIP := net.ParseIP("10.28.0.27").To4()
|
||||
|
||||
suite.Require().NoError(conn.Address.New(&rtnetlink.AddressMessage{
|
||||
Family: unix.AF_INET,
|
||||
PrefixLength: 32,
|
||||
Scope: unix.RT_SCOPE_UNIVERSE,
|
||||
Index: uint32(iface.Index),
|
||||
Attributes: rtnetlink.AddressAttributes{
|
||||
Address: localIP,
|
||||
Local: localIP,
|
||||
},
|
||||
}))
|
||||
|
||||
def := network.NewRouteSpec(network.NamespaceName, "default")
|
||||
*def.TypedSpec() = network.RouteSpecSpec{
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Destination: netaddr.IPPrefix{},
|
||||
Gateway: netaddr.MustParseIP("10.28.0.1"),
|
||||
Source: netaddr.MustParseIPPrefix("10.28.0.27/32"),
|
||||
Table: nethelpers.TableMain,
|
||||
OutLinkName: dummyInterface,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Priority: 1048576,
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
def.TypedSpec().Normalize()
|
||||
|
||||
host := network.NewRouteSpec(network.NamespaceName, "aninterface")
|
||||
*host.TypedSpec() = network.RouteSpecSpec{
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Destination: netaddr.MustParseIPPrefix("10.28.0.1/32"),
|
||||
Gateway: netaddr.MustParseIP("0.0.0.0"),
|
||||
Source: netaddr.MustParseIPPrefix("10.28.0.27/32"),
|
||||
Table: nethelpers.TableMain,
|
||||
OutLinkName: dummyInterface,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Priority: 1048576,
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
host.TypedSpec().Normalize()
|
||||
|
||||
for _, res := range []resource.Resource{def, host} {
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, res), "%v", res.Spec())
|
||||
}
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
if err := suite.assertRoute(netaddr.IPPrefix{}, netaddr.MustParseIP("10.28.0.1"), func(route rtnetlink.RouteMessage) error {
|
||||
suite.Assert().Nil(route.Attributes.Dst)
|
||||
suite.Assert().EqualValues(1048576, route.Attributes.Priority)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return suite.assertRoute(netaddr.MustParseIPPrefix("10.28.0.1/32"), netaddr.IP{}, func(route rtnetlink.RouteMessage) error {
|
||||
suite.Assert().Nil(route.Attributes.Gateway)
|
||||
suite.Assert().EqualValues(1048576, route.Attributes.Priority)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
|
||||
// teardown the routes
|
||||
for {
|
||||
ready, err := suite.state.Teardown(suite.ctx, def.Metadata())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
if ready {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
for {
|
||||
ready, err := suite.state.Teardown(suite.ctx, host.Metadata())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
if ready {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// torn down route should be removed immediately
|
||||
suite.Assert().NoError(suite.assertNoRoute(netaddr.IPPrefix{}, netaddr.MustParseIP("10.28.0.1")))
|
||||
suite.Assert().NoError(suite.assertNoRoute(netaddr.MustParseIPPrefix("10.28.0.1/32"), netaddr.IP{}))
|
||||
|
||||
suite.Require().NoError(suite.state.Destroy(suite.ctx, def.Metadata()))
|
||||
}
|
||||
|
||||
func (suite *RouteSpecSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewRouteSpec(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestRouteSpecSuite(t *testing.T) {
|
||||
suite.Run(t, new(RouteSpecSuite))
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ func (suite *RouteStatusSuite) assertRoutes(requiredIDs []string, check func(*ne
|
||||
func (suite *RouteStatusSuite) TestRoutes() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertRoutes([]string{"127.0.0.0/8/"}, func(r *network.RouteStatus) error {
|
||||
return suite.assertRoutes([]string{"/127.0.0.0/8"}, func(r *network.RouteStatus) error {
|
||||
suite.Assert().True(r.TypedSpec().Source.IP.IsLoopback())
|
||||
suite.Assert().Equal("lo", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(nethelpers.TableLocal, r.TypedSpec().Table)
|
||||
@ -112,6 +112,14 @@ func (suite *RouteStatusSuite) TestRoutes() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *RouteStatusSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
}
|
||||
|
||||
func TestRouteStatusSuite(t *testing.T) {
|
||||
suite.Run(t, new(RouteStatusSuite))
|
||||
}
|
||||
|
@ -137,6 +137,20 @@ func (suite *StatusSuite) TestEtcFiles() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *StatusSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewNodeAddress(network.NamespaceName, "bar")))
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewResolverStatus(network.NamespaceName, "bar")))
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewHostnameStatus(network.NamespaceName, "bar")))
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), files.NewEtcFileStatus(files.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestStatusSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusSuite))
|
||||
}
|
||||
|
@ -198,6 +198,25 @@ func (suite *TimeServerConfigSuite) TestMachineConfiguration() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *TimeServerConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
err := suite.state.Create(context.Background(), config.NewMachineConfig(&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
}))
|
||||
if state.IsConflictError(err) {
|
||||
err = suite.state.Destroy(context.Background(), config.NewMachineConfig(nil).Metadata())
|
||||
}
|
||||
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func TestTimeServerConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(TimeServerConfigSuite))
|
||||
}
|
||||
|
@ -153,6 +153,17 @@ func (suite *TimeServerMergeSuite) TestMerge() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *TimeServerMergeSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewTimeServerSpec(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestTimeServerMergeSuite(t *testing.T) {
|
||||
suite.Run(t, new(TimeServerMergeSuite))
|
||||
}
|
||||
|
@ -100,6 +100,17 @@ func (suite *TimeServerSpecSuite) TestSpec() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *TimeServerSpecSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewTimeServerSpec(network.NamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestTimeServerSpecSuite(t *testing.T) {
|
||||
suite.Run(t, new(TimeServerSpecSuite))
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/talos-systems/talos/internal/pkg/etcd"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
"github.com/talos-systems/talos/pkg/resources/secrets"
|
||||
"github.com/talos-systems/talos/pkg/resources/time"
|
||||
"github.com/talos-systems/talos/pkg/resources/v1alpha1"
|
||||
@ -38,9 +39,9 @@ func (ctrl *EtcdController) Inputs() []controller.Input {
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: v1alpha1.NamespaceName,
|
||||
Type: v1alpha1.ServiceType,
|
||||
ID: pointer.ToString("networkd"),
|
||||
Namespace: network.NamespaceName,
|
||||
Type: network.StatusType,
|
||||
ID: pointer.ToString(network.StatusID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
@ -88,8 +89,8 @@ func (ctrl *EtcdController) Run(ctx context.Context, r controller.Runtime, logge
|
||||
|
||||
etcdRoot := etcdRootRes.(*secrets.Root).EtcdSpec()
|
||||
|
||||
// wait for networkd to be healthy as it might change IPs/hostname
|
||||
networkdResource, err := r.Get(ctx, resource.NewMetadata(v1alpha1.NamespaceName, v1alpha1.ServiceType, "networkd", resource.VersionUndefined))
|
||||
// wait for network to be ready as it might change IPs/hostname
|
||||
networkResource, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.StatusType, network.StatusID, resource.VersionUndefined))
|
||||
if err != nil {
|
||||
if state.IsNotFoundError(err) {
|
||||
continue
|
||||
@ -98,7 +99,9 @@ func (ctrl *EtcdController) Run(ctx context.Context, r controller.Runtime, logge
|
||||
return err
|
||||
}
|
||||
|
||||
if !networkdResource.(*v1alpha1.Service).Healthy() {
|
||||
networkStatus := networkResource.(*network.Status).TypedSpec()
|
||||
|
||||
if !(networkStatus.AddressReady && networkStatus.HostnameReady) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/talos-systems/talos/internal/pkg/kubeconfig"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
"github.com/talos-systems/talos/pkg/resources/secrets"
|
||||
timeresource "github.com/talos-systems/talos/pkg/resources/time"
|
||||
"github.com/talos-systems/talos/pkg/resources/v1alpha1"
|
||||
@ -44,21 +45,9 @@ func (ctrl *KubernetesController) Name() string {
|
||||
func (ctrl *KubernetesController) Inputs() []controller.Input {
|
||||
return []controller.Input{
|
||||
{
|
||||
Namespace: secrets.NamespaceName,
|
||||
Type: secrets.RootType,
|
||||
ID: pointer.ToString(secrets.RootKubernetesID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: v1alpha1.NamespaceName,
|
||||
Type: v1alpha1.ServiceType,
|
||||
ID: pointer.ToString("networkd"),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: v1alpha1.NamespaceName,
|
||||
Type: timeresource.StatusType,
|
||||
ID: pointer.ToString(timeresource.StatusID),
|
||||
Namespace: network.NamespaceName,
|
||||
Type: network.StatusType,
|
||||
ID: pointer.ToString(network.StatusID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}
|
||||
@ -78,6 +67,50 @@ func (ctrl *KubernetesController) Outputs() []controller.Output {
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (ctrl *KubernetesController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
// wait for the network to be ready first, then switch to regular inputs
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-r.EventCh():
|
||||
}
|
||||
// wait for network to be ready as it might change IPs/hostname
|
||||
networkResource, err := r.Get(ctx, resource.NewMetadata(network.NamespaceName, network.StatusType, network.StatusID, resource.VersionUndefined))
|
||||
if err != nil {
|
||||
if state.IsNotFoundError(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
networkStatus := networkResource.(*network.Status).TypedSpec()
|
||||
|
||||
if networkStatus.AddressReady && networkStatus.HostnameReady {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// switch to regular inputs once the network is ready
|
||||
if err := r.UpdateInputs([]controller.Input{
|
||||
{
|
||||
Namespace: secrets.NamespaceName,
|
||||
Type: secrets.RootType,
|
||||
ID: pointer.ToString(secrets.RootKubernetesID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: v1alpha1.NamespaceName,
|
||||
Type: timeresource.StatusType,
|
||||
ID: pointer.ToString(timeresource.StatusID),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error updating inputs: %w", err)
|
||||
}
|
||||
|
||||
r.QueueReconcile()
|
||||
|
||||
refreshTicker := time.NewTicker(KubernetesCertificateValidityDuration / 2)
|
||||
defer refreshTicker.Stop()
|
||||
|
||||
@ -104,20 +137,6 @@ func (ctrl *KubernetesController) Run(ctx context.Context, r controller.Runtime,
|
||||
|
||||
k8sRoot := k8sRootRes.(*secrets.Root).KubernetesSpec()
|
||||
|
||||
// wait for networkd to be healthy as it might change IPs/hostname
|
||||
networkdResource, err := r.Get(ctx, resource.NewMetadata(v1alpha1.NamespaceName, v1alpha1.ServiceType, "networkd", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
if state.IsNotFoundError(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if !networkdResource.(*v1alpha1.Service).Healthy() {
|
||||
continue
|
||||
}
|
||||
|
||||
// wait for time sync as certs depend on current time
|
||||
timeSyncResource, err := r.Get(ctx, resource.NewMetadata(v1alpha1.NamespaceName, timeresource.StatusType, timeresource.StatusID, resource.VersionUndefined))
|
||||
if err != nil {
|
||||
|
@ -15,10 +15,10 @@ import (
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
)
|
||||
|
||||
// Metadata holds packet metadata info.
|
||||
@ -113,7 +113,7 @@ func (p *Packet) Configuration(ctx context.Context) ([]byte, error) {
|
||||
}
|
||||
|
||||
// translate the int returned from bond mode metadata to the type needed by networkd
|
||||
bondMode := nic.BondMode(uint8(unmarshalledMetadataConfig.Network.Bonding.Mode))
|
||||
bondMode := nethelpers.BondMode(uint8(unmarshalledMetadataConfig.Network.Bonding.Mode))
|
||||
|
||||
// determine bond name and build list of interfaces enslaved by the bond
|
||||
devicesInBond := []string{}
|
||||
|
@ -64,7 +64,6 @@ func (*Sequencer) ApplyConfiguration(r runtime.Runtime, req *machineapi.ApplyCon
|
||||
).Append(
|
||||
"cleanup",
|
||||
StopAllPods,
|
||||
StopNetworkd,
|
||||
).AppendList(
|
||||
stopAllPhaselist(r),
|
||||
).Append(
|
||||
@ -114,11 +113,7 @@ func (*Sequencer) Initialize(r runtime.Runtime) []runtime.Phase {
|
||||
WriteIMAPolicy,
|
||||
).Append(
|
||||
"etc",
|
||||
CreateEtcNetworkFiles,
|
||||
CreateOSReleaseFile,
|
||||
).Append(
|
||||
"discoverNetwork",
|
||||
SetupDiscoveryNetwork,
|
||||
).AppendWhen(
|
||||
r.State().Machine().Installed(),
|
||||
"mountSystem",
|
||||
@ -130,12 +125,6 @@ func (*Sequencer) Initialize(r runtime.Runtime) []runtime.Phase {
|
||||
r.State().Machine().Installed(),
|
||||
"unmountSystem",
|
||||
UnmountStatePartition,
|
||||
).Append(
|
||||
"resetNetwork",
|
||||
ResetNetwork,
|
||||
).Append(
|
||||
"setupNetwork",
|
||||
SetupDiscoveryNetwork,
|
||||
)
|
||||
}
|
||||
|
||||
@ -287,7 +276,6 @@ func (*Sequencer) Reboot(r runtime.Runtime) []runtime.Phase {
|
||||
phases := PhaseList{}.Append(
|
||||
"cleanup",
|
||||
StopAllPods,
|
||||
StopNetworkd,
|
||||
).
|
||||
AppendList(stopAllPhaselist(r)).
|
||||
Append("reboot", Reboot)
|
||||
@ -315,12 +303,10 @@ func (*Sequencer) Reset(r runtime.Runtime, in runtime.ResetOptions) []runtime.Ph
|
||||
in.GetGraceful(),
|
||||
"cleanup",
|
||||
RemoveAllPods,
|
||||
StopNetworkd,
|
||||
).AppendWhen(
|
||||
!in.GetGraceful(),
|
||||
"cleanup",
|
||||
StopAllPods,
|
||||
StopNetworkd,
|
||||
).AppendWhen(
|
||||
in.GetGraceful() && (r.Config().Machine().Type() != machine.TypeJoin),
|
||||
"leave",
|
||||
@ -355,7 +341,6 @@ func (*Sequencer) Shutdown(r runtime.Runtime) []runtime.Phase {
|
||||
Append(
|
||||
"cleanup",
|
||||
StopAllPods,
|
||||
StopNetworkd,
|
||||
).
|
||||
AppendList(stopAllPhaselist(r)).
|
||||
Append("shutdown", Shutdown)
|
||||
@ -374,7 +359,6 @@ func (*Sequencer) StageUpgrade(r runtime.Runtime, in *machineapi.UpgradeRequest)
|
||||
phases = phases.Append(
|
||||
"cleanup",
|
||||
StopAllPods,
|
||||
StopNetworkd,
|
||||
).AppendWhen(
|
||||
!in.GetPreserve() && (r.Config().Machine().Type() != machine.TypeJoin),
|
||||
"leave",
|
||||
@ -405,12 +389,10 @@ func (*Sequencer) Upgrade(r runtime.Runtime, in *machineapi.UpgradeRequest) []ru
|
||||
!in.GetPreserve(),
|
||||
"cleanup",
|
||||
RemoveAllPods,
|
||||
StopNetworkd,
|
||||
).AppendWhen(
|
||||
in.GetPreserve(),
|
||||
"cleanup",
|
||||
StopAllPods,
|
||||
StopNetworkd,
|
||||
).AppendWhen(
|
||||
!in.GetPreserve() && (r.Config().Machine().Type() != machine.TypeJoin),
|
||||
"leave",
|
||||
|
@ -48,7 +48,6 @@ import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/events"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/services"
|
||||
"github.com/talos-systems/talos/internal/app/maintenance"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/networkd"
|
||||
"github.com/talos-systems/talos/internal/pkg/containers/cri/containerd"
|
||||
"github.com/talos-systems/talos/internal/pkg/cri"
|
||||
"github.com/talos-systems/talos/internal/pkg/etcd"
|
||||
@ -307,16 +306,6 @@ HOME_URL="https://docs.talos-systems.com/"
|
||||
BUG_REPORT_URL="https://github.com/talos-systems/talos/issues"
|
||||
`
|
||||
|
||||
// Hosts creates a persistent and writable /etc/hosts file.
|
||||
func Hosts() (err error) {
|
||||
return createBindMount(filepath.Join(constants.SystemEtcPath, "hosts"), "/etc/hosts")
|
||||
}
|
||||
|
||||
// ResolvConf creates a persistent and writable /etc/resolv.conf file.
|
||||
func ResolvConf() (err error) {
|
||||
return createBindMount(filepath.Join(constants.SystemEtcPath, "resolv.conf"), "/etc/resolv.conf")
|
||||
}
|
||||
|
||||
// OSRelease renders a valid /etc/os-release file and writes it to disk. The
|
||||
// node's OS Image field is reported by the node from /etc/os-release.
|
||||
func OSRelease() (err error) {
|
||||
@ -384,19 +373,6 @@ func createBindMount(src, dst string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateEtcNetworkFiles represents the CreateEtcNetworkFiles task.
|
||||
func CreateEtcNetworkFiles(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
// Create /etc/resolv.conf.
|
||||
if err = ResolvConf(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create /etc/hosts
|
||||
return Hosts()
|
||||
}, "createEtcNetworkFiles"
|
||||
}
|
||||
|
||||
// CreateOSReleaseFile represents the CreateOSReleaseFile task.
|
||||
func CreateOSReleaseFile(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
@ -405,21 +381,6 @@ func CreateOSReleaseFile(seq runtime.Sequence, data interface{}) (runtime.TaskEx
|
||||
}, "createOSReleaseFile"
|
||||
}
|
||||
|
||||
// SetupDiscoveryNetwork represents the task for setting up the initial network.
|
||||
func SetupDiscoveryNetwork(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
nwd, err := networkd.New(logger, r.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
return nwd.Configure(ctx)
|
||||
}, "setupDiscoveryNetwork"
|
||||
}
|
||||
|
||||
// LoadConfig represents the LoadConfig task.
|
||||
func LoadConfig(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
@ -568,20 +529,6 @@ func ValidateConfig(seq runtime.Sequence, data interface{}) (runtime.TaskExecuti
|
||||
}, "validateConfig"
|
||||
}
|
||||
|
||||
// ResetNetwork resets the network.
|
||||
func ResetNetwork(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
nwd, err := networkd.New(logger, r.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nwd.Reset()
|
||||
|
||||
return nil
|
||||
}, "resetNetwork"
|
||||
}
|
||||
|
||||
// SetUserEnvVars represents the SetUserEnvVars task.
|
||||
func SetUserEnvVars(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
@ -630,7 +577,6 @@ func StartAllServices(seq runtime.Sequence, data interface{}) (runtime.TaskExecu
|
||||
|
||||
svcs.Load(
|
||||
&services.APID{},
|
||||
&services.Networkd{},
|
||||
&services.CRI{},
|
||||
&services.Kubelet{},
|
||||
)
|
||||
@ -669,14 +615,6 @@ func StartAllServices(seq runtime.Sequence, data interface{}) (runtime.TaskExecu
|
||||
}, "startAllServices"
|
||||
}
|
||||
|
||||
// StopNetworkd represents the StopNetworkd task.
|
||||
func StopNetworkd(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
// stop networkd so that it gives up on VIP lease
|
||||
return system.Services(nil).Stop(ctx, "networkd")
|
||||
}, "stopNetworkd"
|
||||
}
|
||||
|
||||
// StopServicesForUpgrade represents the StopServicesForUpgrade task.
|
||||
func StopServicesForUpgrade(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) {
|
||||
return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) {
|
||||
|
@ -77,21 +77,20 @@ func (ctrl *Controller) Run(ctx context.Context) error {
|
||||
&k8s.ManifestApplyController{},
|
||||
&k8s.RenderSecretsStaticPodController{},
|
||||
&network.AddressConfigController{
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
|
||||
},
|
||||
&network.AddressMergeController{},
|
||||
&network.AddressSpecController{},
|
||||
&network.AddressStatusController{},
|
||||
// TODO: disabled to avoid conflict with networkd
|
||||
// &network.EtcFileController{},
|
||||
&network.EtcFileController{},
|
||||
&network.HostnameConfigController{
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
},
|
||||
&network.HostnameMergeController{},
|
||||
// TODO: disabled to avoid conflict with networkd
|
||||
// &network.HostnameSpecController{
|
||||
// V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
|
||||
// },
|
||||
&network.HostnameSpecController{
|
||||
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
|
||||
},
|
||||
&network.LinkConfigController{
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
},
|
||||
@ -102,6 +101,13 @@ func (ctrl *Controller) Run(ctx context.Context) error {
|
||||
&network.OperatorConfigController{
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
},
|
||||
&network.OperatorSpecController{
|
||||
V1alpha1Platform: ctrl.v1alpha1Runtime.State().Platform(),
|
||||
State: ctrl.v1alpha1Runtime.State().V1Alpha2().Resources(),
|
||||
},
|
||||
&network.PlatformConfigController{
|
||||
V1alpha1Platform: ctrl.v1alpha1Runtime.State().Platform(),
|
||||
},
|
||||
&network.ResolverConfigController{
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
},
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/copy"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
"github.com/talos-systems/talos/pkg/resources/time"
|
||||
)
|
||||
|
||||
@ -71,6 +72,7 @@ func (o *APID) PostFunc(r runtime.Runtime, state events.ServiceState) (err error
|
||||
func (o *APID) Condition(r runtime.Runtime) conditions.Condition {
|
||||
conds := []conditions.Condition{
|
||||
time.NewSyncCondition(r.State().V1Alpha2().Resources()),
|
||||
network.NewReadyCondition(r.State().V1Alpha2().Resources(), network.AddressReady, network.HostnameReady),
|
||||
}
|
||||
|
||||
if r.Config().Machine().Type() == machine.TypeJoin {
|
||||
@ -82,7 +84,7 @@ func (o *APID) Condition(r runtime.Runtime) conditions.Condition {
|
||||
|
||||
// DependsOn implements the Service interface.
|
||||
func (o *APID) DependsOn(r runtime.Runtime) []string {
|
||||
return []string{"containerd", "networkd"}
|
||||
return []string{"containerd"}
|
||||
}
|
||||
|
||||
// Runner implements the Service interface.
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/runner/restart"
|
||||
"github.com/talos-systems/talos/pkg/conditions"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
)
|
||||
|
||||
// CRI implements the Service interface. It serves as the concrete type with
|
||||
@ -44,12 +45,12 @@ func (c *CRI) PostFunc(r runtime.Runtime, state events.ServiceState) (err error)
|
||||
|
||||
// Condition implements the Service interface.
|
||||
func (c *CRI) Condition(r runtime.Runtime) conditions.Condition {
|
||||
return nil
|
||||
return network.NewReadyCondition(r.State().V1Alpha2().Resources(), network.AddressReady, network.HostnameReady, network.EtcFilesReady)
|
||||
}
|
||||
|
||||
// DependsOn implements the Service interface.
|
||||
func (c *CRI) DependsOn(r runtime.Runtime) []string {
|
||||
return []string{"networkd"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Runner implements the Service interface.
|
||||
|
@ -40,6 +40,7 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/conditions"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
timeresource "github.com/talos-systems/talos/pkg/resources/time"
|
||||
)
|
||||
|
||||
@ -113,12 +114,15 @@ func (e *Etcd) PostFunc(r runtime.Runtime, state events.ServiceState) (err error
|
||||
|
||||
// Condition implements the Service interface.
|
||||
func (e *Etcd) Condition(r runtime.Runtime) conditions.Condition {
|
||||
return timeresource.NewSyncCondition(r.State().V1Alpha2().Resources())
|
||||
return conditions.WaitForAll(
|
||||
timeresource.NewSyncCondition(r.State().V1Alpha2().Resources()),
|
||||
network.NewReadyCondition(r.State().V1Alpha2().Resources(), network.AddressReady, network.HostnameReady, network.EtcFilesReady),
|
||||
)
|
||||
}
|
||||
|
||||
// DependsOn implements the Service interface.
|
||||
func (e *Etcd) DependsOn(r runtime.Runtime) []string {
|
||||
return []string{"cri", "networkd"}
|
||||
return []string{"cri"}
|
||||
}
|
||||
|
||||
// Runner implements the Service interface.
|
||||
|
@ -35,6 +35,7 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/argsbuilder"
|
||||
"github.com/talos-systems/talos/pkg/conditions"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
timeresource "github.com/talos-systems/talos/pkg/resources/time"
|
||||
)
|
||||
|
||||
@ -127,12 +128,15 @@ func (k *Kubelet) PostFunc(r runtime.Runtime, state events.ServiceState) (err er
|
||||
|
||||
// Condition implements the Service interface.
|
||||
func (k *Kubelet) Condition(r runtime.Runtime) conditions.Condition {
|
||||
return timeresource.NewSyncCondition(r.State().V1Alpha2().Resources())
|
||||
return conditions.WaitForAll(
|
||||
timeresource.NewSyncCondition(r.State().V1Alpha2().Resources()),
|
||||
network.NewReadyCondition(r.State().V1Alpha2().Resources(), network.AddressReady, network.HostnameReady, network.EtcFilesReady),
|
||||
)
|
||||
}
|
||||
|
||||
// DependsOn implements the Service interface.
|
||||
func (k *Kubelet) DependsOn(r runtime.Runtime) []string {
|
||||
return []string{"cri", "networkd"}
|
||||
return []string{"cri"}
|
||||
}
|
||||
|
||||
// Runner implements the Service interface.
|
||||
|
@ -1,123 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//nolint:golint
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/events"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/health"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/runner"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/runner/goroutine"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/runner/restart"
|
||||
"github.com/talos-systems/talos/internal/app/networkd"
|
||||
"github.com/talos-systems/talos/pkg/conditions"
|
||||
"github.com/talos-systems/talos/pkg/grpc/dialer"
|
||||
healthapi "github.com/talos-systems/talos/pkg/machinery/api/health"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// Networkd implements the Service interface. It serves as the concrete type with
|
||||
// the required methods.
|
||||
type Networkd struct{}
|
||||
|
||||
// ID implements the Service interface.
|
||||
func (n *Networkd) ID(r runtime.Runtime) string {
|
||||
return "networkd"
|
||||
}
|
||||
|
||||
// PreFunc implements the Service interface.
|
||||
func (n *Networkd) PreFunc(ctx context.Context, r runtime.Runtime) error {
|
||||
return os.MkdirAll(filepath.Dir(constants.NetworkSocketPath), 0o750)
|
||||
}
|
||||
|
||||
// PostFunc implements the Service interface.
|
||||
func (n *Networkd) PostFunc(r runtime.Runtime, state events.ServiceState) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Condition implements the Service interface.
|
||||
func (n *Networkd) Condition(r runtime.Runtime) conditions.Condition {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DependsOn implements the Service interface.
|
||||
func (n *Networkd) DependsOn(r runtime.Runtime) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Networkd) Runner(r runtime.Runtime) (runner.Runner, error) {
|
||||
return restart.New(goroutine.NewRunner(
|
||||
r,
|
||||
"networkd",
|
||||
networkd.Main,
|
||||
runner.WithLoggingManager(r.Logging()),
|
||||
),
|
||||
restart.WithType(restart.Forever),
|
||||
), nil
|
||||
}
|
||||
|
||||
// HealthFunc implements the HealthcheckedService interface.
|
||||
func (n *Networkd) HealthFunc(r runtime.Runtime) health.Check {
|
||||
return func(ctx context.Context) error {
|
||||
var (
|
||||
conn *grpc.ClientConn
|
||||
err error
|
||||
hcResp *healthapi.HealthCheckResponse
|
||||
readyResp *healthapi.ReadyCheckResponse
|
||||
)
|
||||
|
||||
conn, err = grpc.DialContext(
|
||||
ctx,
|
||||
fmt.Sprintf("%s://%s", "unix", constants.NetworkSocketPath),
|
||||
grpc.WithInsecure(),
|
||||
grpc.WithContextDialer(dialer.DialUnix()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close() //nolint:errcheck
|
||||
|
||||
nClient := healthapi.NewHealthClient(conn)
|
||||
if readyResp, err = nClient.Ready(ctx, &empty.Empty{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if readyResp.Messages[0].Status != healthapi.ReadyCheck_READY {
|
||||
return errors.New("networkd is not ready")
|
||||
}
|
||||
|
||||
if hcResp, err = nClient.Check(ctx, &empty.Empty{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hcResp.Messages[0].Status == healthapi.HealthCheck_SERVING {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("networkd is unhealthy: %s", hcResp.Messages[0].Status.String())
|
||||
|
||||
if r.Config().Debug() {
|
||||
log.Printf("DEBUG: %s", msg)
|
||||
}
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// HealthSettings implements the HealthcheckedService interface.
|
||||
func (n *Networkd) HealthSettings(runtime.Runtime) *health.Settings {
|
||||
return &health.DefaultSettings
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package services_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/services"
|
||||
)
|
||||
|
||||
func TestNetworkdInterfaces(t *testing.T) {
|
||||
assert.Implements(t, (*system.HealthcheckedService)(nil), new(services.Networkd))
|
||||
}
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/system/runner/restart"
|
||||
"github.com/talos-systems/talos/pkg/conditions"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/resources/network"
|
||||
timeresource "github.com/talos-systems/talos/pkg/resources/time"
|
||||
)
|
||||
|
||||
@ -48,12 +49,15 @@ func (t *Trustd) PostFunc(r runtime.Runtime, state events.ServiceState) (err err
|
||||
|
||||
// Condition implements the Service interface.
|
||||
func (t *Trustd) Condition(r runtime.Runtime) conditions.Condition {
|
||||
return timeresource.NewSyncCondition(r.State().V1Alpha2().Resources())
|
||||
return conditions.WaitForAll(
|
||||
timeresource.NewSyncCondition(r.State().V1Alpha2().Resources()),
|
||||
network.NewReadyCondition(r.State().V1Alpha2().Resources(), network.AddressReady, network.HostnameReady),
|
||||
)
|
||||
}
|
||||
|
||||
// DependsOn implements the Service interface.
|
||||
func (t *Trustd) DependsOn(r runtime.Runtime) []string {
|
||||
return []string{"containerd", "networkd"}
|
||||
return []string{"containerd"}
|
||||
}
|
||||
|
||||
func (t *Trustd) Runner(r runtime.Runtime) (runner.Runner, error) {
|
||||
|
@ -1,79 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package networkd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/networkd"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/reg"
|
||||
"github.com/talos-systems/talos/pkg/grpc/factory"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// Main is the entrypoint into networkd.
|
||||
func Main(ctx context.Context, r runtime.Runtime, logOutput io.Writer) error {
|
||||
logger := log.New(logOutput, "", log.Lshortfile|log.Ldate|log.Lmicroseconds|log.Ltime)
|
||||
|
||||
defer logger.Println("networkd stopped")
|
||||
|
||||
return run(ctx, r, logger)
|
||||
}
|
||||
|
||||
func run(ctx context.Context, r runtime.Runtime, logger *log.Logger) error {
|
||||
var eg errgroup.Group
|
||||
|
||||
logger.Println("starting initial network configuration")
|
||||
|
||||
nwd, err := networkd.New(logger, r.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = nwd.Configure(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registrator, err := reg.NewRegistrator(nwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = nwd.RunControllers(ctx, &eg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Println("completed initial network configuration")
|
||||
|
||||
nwd.Renew(ctx)
|
||||
|
||||
server := factory.NewServer(
|
||||
registrator,
|
||||
factory.WithLog("", logger.Writer()),
|
||||
)
|
||||
|
||||
listener, err := factory.NewListener(
|
||||
factory.Network("unix"),
|
||||
factory.SocketPath(constants.NetworkSocketPath),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
return server.Serve(listener)
|
||||
})
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
server.GracefulStop()
|
||||
|
||||
return eg.Wait()
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package address
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Addressing provides an interface for abstracting the underlying network
|
||||
// addressing configuration. Currently dhcp(v4) and static methods are
|
||||
// supported.
|
||||
type Addressing interface {
|
||||
Address() *net.IPNet
|
||||
Discover(context.Context, *log.Logger, *net.Interface) error
|
||||
Family() int
|
||||
Hostname() string
|
||||
Link() *net.Interface
|
||||
MTU() uint32
|
||||
Mask() net.IPMask
|
||||
Name() string
|
||||
Resolvers() []net.IP
|
||||
Routes() []*Route
|
||||
Scope() uint8
|
||||
TTL() time.Duration
|
||||
Valid() bool
|
||||
}
|
||||
|
||||
// Route is a representation of a network route.
|
||||
type Route struct {
|
||||
// Destination is the destination network this route provides.
|
||||
Destination *net.IPNet
|
||||
|
||||
// Gateway is the router through which the destination may be reached.
|
||||
// This option is exclusive of Interface
|
||||
Gateway net.IP
|
||||
|
||||
// Interface indicates the route is an interface route, and traffic destinted for the Gateway should be sent through the given network interface.
|
||||
// This option is exclusive of Gateway.
|
||||
Interface string
|
||||
|
||||
// Metric indicates the "distance" to the destination through this route.
|
||||
// This is an integer which allows the control of priority in the case of multiple routes to the same destination.
|
||||
Metric uint32
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package address
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
const dhcpReceivedRouteMetric uint32 = 1024
|
||||
|
||||
// DHCP4 implements the Addressing interface.
|
||||
type DHCP4 struct {
|
||||
Offer *dhcpv4.DHCPv4
|
||||
Ack *dhcpv4.DHCPv4
|
||||
NetIf *net.Interface
|
||||
DHCPOptions config.DHCPOptions
|
||||
Mtu int
|
||||
RouteList []config.Route
|
||||
}
|
||||
|
||||
// Name returns back the name of the address method.
|
||||
func (d *DHCP4) Name() string {
|
||||
return "dhcp4"
|
||||
}
|
||||
|
||||
// Link returns the underlying net.Interface that this address
|
||||
// method is configured for.
|
||||
func (d *DHCP4) Link() *net.Interface {
|
||||
return d.NetIf
|
||||
}
|
||||
|
||||
// Discover handles the DHCP client exchange stores the DHCP Ack.
|
||||
func (d *DHCP4) Discover(ctx context.Context, logger *log.Logger, link *net.Interface) error {
|
||||
d.NetIf = link
|
||||
err := d.discover(ctx, logger)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Address returns back the IP address from the received DHCP offer.
|
||||
func (d *DHCP4) Address() *net.IPNet {
|
||||
return &net.IPNet{
|
||||
IP: d.Ack.YourIPAddr,
|
||||
Mask: d.Mask(),
|
||||
}
|
||||
}
|
||||
|
||||
// Mask returns the netmask from the DHCP offer.
|
||||
func (d *DHCP4) Mask() net.IPMask {
|
||||
return d.Ack.SubnetMask()
|
||||
}
|
||||
|
||||
// MTU returs the MTU size from the DHCP offer.
|
||||
func (d *DHCP4) MTU() uint32 {
|
||||
mtuReturn := uint32(d.NetIf.MTU)
|
||||
|
||||
if d.Ack != nil {
|
||||
// TODO do we need to implement dhcpv4.GetUint32 upstream?
|
||||
mtu, err := dhcpv4.GetUint16(dhcpv4.OptionInterfaceMTU, d.Ack.Options)
|
||||
if err == nil {
|
||||
mtuReturn = uint32(mtu)
|
||||
}
|
||||
}
|
||||
|
||||
// override with any non-zero Mtu value passed into the dhcp object
|
||||
if uint32(d.Mtu) > 0 {
|
||||
mtuReturn = uint32(d.Mtu)
|
||||
}
|
||||
|
||||
return mtuReturn
|
||||
}
|
||||
|
||||
// TTL denotes how long a DHCP offer is valid for.
|
||||
func (d *DHCP4) TTL() time.Duration {
|
||||
if d.Ack == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return d.Ack.IPAddressLeaseTime(time.Minute * 30)
|
||||
}
|
||||
|
||||
// Family qualifies the address as ipv4 or ipv6.
|
||||
func (d *DHCP4) Family() int {
|
||||
return unix.AF_INET
|
||||
}
|
||||
|
||||
// Scope sets the address scope.
|
||||
func (d *DHCP4) Scope() uint8 {
|
||||
return unix.RT_SCOPE_UNIVERSE
|
||||
}
|
||||
|
||||
// Valid denotes if this address method should be used.
|
||||
func (d *DHCP4) Valid() bool {
|
||||
return d.Ack != nil
|
||||
}
|
||||
|
||||
// Routes aggregates all Routers and ClasslessStaticRoutes retrieved from
|
||||
// the DHCP offer.
|
||||
// rfc3442:
|
||||
// If the DHCP server returns both a Classless Static Routes option and
|
||||
// a Router option, the DHCP client MUST ignore the Router option.
|
||||
func (d *DHCP4) Routes() (routes []*Route) {
|
||||
metric := dhcpReceivedRouteMetric
|
||||
|
||||
if d.DHCPOptions != nil && d.DHCPOptions.RouteMetric() != 0 {
|
||||
metric = d.DHCPOptions.RouteMetric()
|
||||
}
|
||||
|
||||
defRoute := &net.IPNet{
|
||||
IP: net.IPv4zero,
|
||||
Mask: net.IPv4Mask(0, 0, 0, 0),
|
||||
}
|
||||
|
||||
for _, router := range d.Ack.Router() {
|
||||
routes = append(routes, &Route{
|
||||
Destination: defRoute,
|
||||
Gateway: router,
|
||||
Metric: metric,
|
||||
})
|
||||
}
|
||||
|
||||
// overwrite router option if classless routes were provided.
|
||||
if len(d.Ack.ClasslessStaticRoute()) > 0 {
|
||||
routes = []*Route{}
|
||||
|
||||
for _, dhcpRoute := range d.Ack.ClasslessStaticRoute() {
|
||||
routes = append(routes, &Route{
|
||||
Destination: dhcpRoute.Dest,
|
||||
Gateway: dhcpRoute.Router,
|
||||
Metric: metric,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// append any routes that were provided in config
|
||||
for _, route := range d.RouteList {
|
||||
_, ipnet, err := net.ParseCIDR(route.Network())
|
||||
if err != nil {
|
||||
// TODO: we should at least log this failure
|
||||
continue
|
||||
}
|
||||
|
||||
routes = append(routes, &Route{
|
||||
Destination: ipnet,
|
||||
Gateway: net.ParseIP(route.Gateway()),
|
||||
Metric: staticRouteDefaultMetric,
|
||||
})
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
// Resolvers returns the DNS resolvers from the DHCP offer.
|
||||
func (d *DHCP4) Resolvers() []net.IP {
|
||||
return d.Ack.DNS()
|
||||
}
|
||||
|
||||
// Hostname returns the hostname from the DHCP offer.
|
||||
func (d *DHCP4) Hostname() (hostname string) {
|
||||
if d.Ack.HostName() == "" {
|
||||
hostname = fmt.Sprintf("%s-%s", "talos", strings.ReplaceAll(d.Address().IP.String(), ".", "-"))
|
||||
} else {
|
||||
hostname = d.Ack.HostName()
|
||||
}
|
||||
|
||||
if d.Ack.DomainName() != "" {
|
||||
hostname = fmt.Sprintf("%s.%s", strings.Split(hostname, ".")[0], d.Ack.DomainName())
|
||||
}
|
||||
|
||||
return hostname
|
||||
}
|
||||
|
||||
// discover handles the actual DHCP conversation.
|
||||
func (d *DHCP4) discover(ctx context.Context, logger *log.Logger) error {
|
||||
opts := []dhcpv4.OptionCode{
|
||||
dhcpv4.OptionClasslessStaticRoute,
|
||||
dhcpv4.OptionDomainNameServer,
|
||||
dhcpv4.OptionDNSDomainSearchList,
|
||||
dhcpv4.OptionHostName,
|
||||
// TODO: handle these options
|
||||
dhcpv4.OptionNTPServers,
|
||||
dhcpv4.OptionDomainName,
|
||||
}
|
||||
|
||||
// <3 azure
|
||||
// When including dhcp.OptionInterfaceMTU we don't get a dhcp offer back on azure.
|
||||
// So we'll need to explicitly exclude adding this option for azure.
|
||||
if p := procfs.ProcCmdline().Get(constants.KernelParamPlatform).First(); p != nil {
|
||||
if *p != "azure" {
|
||||
opts = append(opts, dhcpv4.OptionInterfaceMTU)
|
||||
}
|
||||
}
|
||||
|
||||
mods := []dhcpv4.Modifier{dhcpv4.WithRequestedOptions(opts...)}
|
||||
clientOpts := []nclient4.ClientOpt{}
|
||||
|
||||
if d.Offer != nil {
|
||||
// do not use broadcast, but send the packet to DHCP server directly
|
||||
addr, err := net.ResolveUDPAddr("udp", d.Offer.ServerIPAddr.String()+":67")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// by default it's set to 0.0.0.0 which actually breaks lease renew
|
||||
d.Offer.ClientIPAddr = d.Offer.YourIPAddr
|
||||
|
||||
clientOpts = append(clientOpts, nclient4.WithServerAddr(addr))
|
||||
}
|
||||
|
||||
// TODO expose this ( nclient4.WithDebugLogger() ) with some
|
||||
// debug logging option
|
||||
cli, err := nclient4.New(d.NetIf.Name, clientOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer cli.Close()
|
||||
|
||||
var lease *nclient4.Lease
|
||||
|
||||
if d.Offer != nil {
|
||||
lease, err = cli.RequestFromOffer(ctx, d.Offer, mods...)
|
||||
} else {
|
||||
lease, err = cli.Request(ctx, mods...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// TODO: Make this a well defined error so we can make it not fatal
|
||||
logger.Printf("failed dhcp request for %q: %v", d.NetIf.Name, err)
|
||||
|
||||
// clear offer if request fails to start with discover sequence next time
|
||||
d.Offer = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Printf("DHCP ACK on %q: %s", d.NetIf.Name, collapseSummary(lease.ACK.Summary()))
|
||||
|
||||
d.Ack = lease.ACK
|
||||
d.Offer = lease.Offer
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func collapseSummary(summary string) string {
|
||||
lines := strings.Split(summary, "\n")[1:]
|
||||
|
||||
for i := range lines {
|
||||
lines[i] = strings.TrimSpace(lines[i])
|
||||
}
|
||||
|
||||
if len(lines) > 0 && lines[len(lines)-1] == "" {
|
||||
lines = lines[:len(lines)-1]
|
||||
}
|
||||
|
||||
return strings.Join(lines, ", ")
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package address
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// DHCP6 implements the Addressing interface.
|
||||
type DHCP6 struct {
|
||||
Reply *dhcpv6.Message
|
||||
NetIf *net.Interface
|
||||
Mtu int
|
||||
}
|
||||
|
||||
// Name returns back the name of the address method.
|
||||
func (d *DHCP6) Name() string {
|
||||
return "dhcp6"
|
||||
}
|
||||
|
||||
// Link returns the underlying net.Interface that this address
|
||||
// method is configured for.
|
||||
func (d *DHCP6) Link() *net.Interface {
|
||||
return d.NetIf
|
||||
}
|
||||
|
||||
// Discover handles the DHCP client exchange stores the DHCP Ack.
|
||||
func (d *DHCP6) Discover(ctx context.Context, logger *log.Logger, link *net.Interface) error {
|
||||
d.NetIf = link
|
||||
err := d.discover(ctx, logger)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Address returns back the IP address from the received DHCP offer.
|
||||
func (d *DHCP6) Address() *net.IPNet {
|
||||
if d.Reply.Options.OneIANA() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &net.IPNet{
|
||||
IP: d.Reply.Options.OneIANA().Options.OneAddress().IPv6Addr,
|
||||
Mask: net.CIDRMask(128, 128),
|
||||
}
|
||||
}
|
||||
|
||||
// Mask returns the netmask from the DHCP offer.
|
||||
func (d *DHCP6) Mask() net.IPMask {
|
||||
return net.CIDRMask(128, 128)
|
||||
}
|
||||
|
||||
// MTU returs the MTU size from the DHCP offer.
|
||||
func (d *DHCP6) MTU() uint32 {
|
||||
if d.Mtu > 0 {
|
||||
return uint32(d.Mtu)
|
||||
}
|
||||
|
||||
return uint32(d.NetIf.MTU)
|
||||
}
|
||||
|
||||
// TTL denotes how long a DHCP offer is valid for.
|
||||
func (d *DHCP6) TTL() time.Duration {
|
||||
if d.Reply == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return d.Reply.Options.OneIANA().Options.OneAddress().ValidLifetime
|
||||
}
|
||||
|
||||
// Family qualifies the address as ipv4 or ipv6.
|
||||
func (d *DHCP6) Family() int {
|
||||
return unix.AF_INET6
|
||||
}
|
||||
|
||||
// Scope sets the address scope.
|
||||
func (d *DHCP6) Scope() uint8 {
|
||||
return unix.RT_SCOPE_UNIVERSE
|
||||
}
|
||||
|
||||
// Valid denotes if this address method should be used.
|
||||
func (d *DHCP6) Valid() bool {
|
||||
return d.Reply != nil && d.Reply.Options.OneIANA() != nil
|
||||
}
|
||||
|
||||
// Routes is not supported on IPv6.
|
||||
func (d *DHCP6) Routes() (routes []*Route) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolvers returns the DNS resolvers from the DHCP offer.
|
||||
func (d *DHCP6) Resolvers() []net.IP {
|
||||
return d.Reply.Options.DNS()
|
||||
}
|
||||
|
||||
// Hostname returns the hostname from the DHCP offer.
|
||||
func (d *DHCP6) Hostname() (hostname string) {
|
||||
fqdn := d.Reply.Options.FQDN()
|
||||
|
||||
if fqdn != nil && fqdn.DomainName != nil {
|
||||
hostname = strings.Join(fqdn.DomainName.Labels, ".")
|
||||
} else {
|
||||
hostname = fmt.Sprintf("%s-%s", "talos", strings.ReplaceAll(d.Address().IP.String(), ":", ""))
|
||||
}
|
||||
|
||||
return hostname
|
||||
}
|
||||
|
||||
// discover handles the actual DHCP conversation.
|
||||
func (d *DHCP6) discover(ctx context.Context, logger *log.Logger) error {
|
||||
if err := waitIPv6LinkReady(logger, d.NetIf); err != nil {
|
||||
logger.Printf("failed waiting for IPv6 readiness: %s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
cli, err := nclient6.New(d.NetIf.Name)
|
||||
if err != nil {
|
||||
logger.Printf("failed to create dhcp6 client: %s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer cli.Close()
|
||||
|
||||
reply, err := cli.RapidSolicit(ctx)
|
||||
if err != nil {
|
||||
// TODO: Make this a well defined error so we can make it not fatal
|
||||
logger.Printf("failed dhcp6 request for %q: %v", d.NetIf.Name, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Printf("DHCP6 REPLY on %q: %s", d.NetIf.Name, collapseSummary(reply.Summary()))
|
||||
|
||||
d.Reply = reply
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitIPv6LinkReady(logger *log.Logger, iface *net.Interface) error {
|
||||
conn, err := rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer conn.Close() //nolint:errcheck
|
||||
|
||||
return retry.Constant(30*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error {
|
||||
ready, err := isIPv6LinkReady(logger, iface, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ready {
|
||||
return retry.ExpectedError(fmt.Errorf("IPv6 address is still tentative"))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// isIPv6LinkReady returns true if the interface has a link-local address
|
||||
// which is not tentative.
|
||||
func isIPv6LinkReady(logger *log.Logger, iface *net.Interface, conn *rtnetlink.Conn) (bool, error) {
|
||||
addrs, err := conn.Address.List()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
if addr.Index != uint32(iface.Index) {
|
||||
continue
|
||||
}
|
||||
|
||||
if addr.Family != unix.AF_INET6 {
|
||||
continue
|
||||
}
|
||||
|
||||
if addr.Attributes.Address.IsLinkLocalUnicast() && (addr.Flags&unix.IFA_F_TENTATIVE == 0) {
|
||||
if addr.Flags&unix.IFA_F_DADFAILED != 0 {
|
||||
logger.Printf("DADFAILED for %v, continuing anyhow", addr.Attributes.Address)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package address
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
)
|
||||
|
||||
const staticRouteDefaultMetric uint32 = 10
|
||||
|
||||
// Static implements the Addressing interface.
|
||||
type Static struct {
|
||||
CIDR string
|
||||
Mtu int
|
||||
FQDN string
|
||||
RouteList []config.Route
|
||||
NetIf *net.Interface
|
||||
NameServers []net.IP
|
||||
}
|
||||
|
||||
// Discover doesnt do anything in the static configuration since all
|
||||
// the necessary configuration data is supplied via config.
|
||||
func (s *Static) Discover(ctx context.Context, logger *log.Logger, link *net.Interface) error {
|
||||
s.NetIf = link
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns back the name of the address method.
|
||||
func (s *Static) Name() string {
|
||||
return "static"
|
||||
}
|
||||
|
||||
// Address returns the IP address.
|
||||
func (s *Static) Address() *net.IPNet {
|
||||
var ip net.IP
|
||||
|
||||
var ipn *net.IPNet
|
||||
|
||||
if s.CIDR != "" {
|
||||
//nolint:errcheck
|
||||
ip, ipn, _ = net.ParseCIDR(s.CIDR)
|
||||
ipn.IP = ip
|
||||
}
|
||||
|
||||
return ipn
|
||||
}
|
||||
|
||||
// Mask returns the netmask.
|
||||
func (s *Static) Mask() net.IPMask {
|
||||
//nolint:errcheck
|
||||
_, ipnet, _ := net.ParseCIDR(s.CIDR)
|
||||
|
||||
return ipnet.Mask
|
||||
}
|
||||
|
||||
// MTU returns the specified MTU.
|
||||
func (s *Static) MTU() uint32 {
|
||||
mtu := uint32(s.Mtu)
|
||||
if mtu == 0 {
|
||||
mtu = uint32(s.NetIf.MTU)
|
||||
}
|
||||
|
||||
return mtu
|
||||
}
|
||||
|
||||
// TTL returns the address lifetime. Since this is static, there is
|
||||
// no TTL (0).
|
||||
func (s *Static) TTL() time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Family qualifies the address as ipv4 or ipv6.
|
||||
func (s *Static) Family() int {
|
||||
if s.Address() == nil {
|
||||
panic("unable to determine address family as address is nil")
|
||||
}
|
||||
|
||||
if s.Address().IP.To4() != nil {
|
||||
return unix.AF_INET
|
||||
}
|
||||
|
||||
return unix.AF_INET6
|
||||
}
|
||||
|
||||
// Scope sets the address scope.
|
||||
func (s *Static) Scope() uint8 {
|
||||
return unix.RT_SCOPE_UNIVERSE
|
||||
}
|
||||
|
||||
// Routes aggregates the specified routes for a given device configuration
|
||||
// TODO: do we need to be explicit on route vs gateway?
|
||||
func (s *Static) Routes() (routes []*Route) {
|
||||
for _, route := range s.RouteList {
|
||||
_, ipnet, err := net.ParseCIDR(route.Network())
|
||||
if err != nil {
|
||||
// TODO: we should at least log the error
|
||||
continue
|
||||
}
|
||||
|
||||
metric := staticRouteDefaultMetric
|
||||
|
||||
if route.Metric() != 0 {
|
||||
metric = route.Metric()
|
||||
}
|
||||
|
||||
routes = append(routes, &Route{
|
||||
Destination: ipnet,
|
||||
Gateway: net.ParseIP(route.Gateway()),
|
||||
Metric: metric,
|
||||
})
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
// Resolvers returns the DNS resolvers.
|
||||
func (s *Static) Resolvers() []net.IP {
|
||||
return s.NameServers
|
||||
}
|
||||
|
||||
// Hostname returns the hostname.
|
||||
func (s *Static) Hostname() string {
|
||||
return s.FQDN
|
||||
}
|
||||
|
||||
// Link returns the underlying net.Interface that this address
|
||||
// method is configured for.
|
||||
func (s Static) Link() *net.Interface {
|
||||
return s.NetIf
|
||||
}
|
||||
|
||||
// Valid denotes if this address method should be used.
|
||||
func (s *Static) Valid() bool {
|
||||
return true
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package networkd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
talosnet "github.com/talos-systems/net"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
)
|
||||
|
||||
// filterInterfaces filters network links by name so we only mange links
|
||||
// we need to.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func filterInterfaces(logger *log.Logger, interfaces []net.Interface) (filtered []net.Interface, err error) {
|
||||
var conn *rtnetlink.Conn
|
||||
|
||||
for _, iface := range interfaces {
|
||||
switch {
|
||||
case strings.HasPrefix(iface.Name, "en"):
|
||||
filtered = append(filtered, iface)
|
||||
case strings.HasPrefix(iface.Name, "eth"):
|
||||
filtered = append(filtered, iface)
|
||||
case strings.HasPrefix(iface.Name, "lo"):
|
||||
filtered = append(filtered, iface)
|
||||
case strings.HasPrefix(iface.Name, "bond"):
|
||||
filtered = append(filtered, iface)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err = rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer conn.Close()
|
||||
|
||||
n := 0 //nolint:wsl
|
||||
for _, iface := range filtered {
|
||||
link, err := conn.Link.Get(uint32(iface.Index))
|
||||
if err != nil {
|
||||
logger.Printf("error getting link %q", iface.Name)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if link.Flags&unix.IFF_UP == unix.IFF_UP && !(link.Flags&unix.IFF_RUNNING == unix.IFF_RUNNING) {
|
||||
logger.Printf("no carrier for link %q", iface.Name)
|
||||
} else {
|
||||
logger.Printf("link %q has carrier signal", iface.Name)
|
||||
filtered[n] = iface
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
filtered = filtered[:n]
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// writeResolvConf generates a /etc/resolv.conf with the specified nameservers.
|
||||
func writeResolvConf(logger *log.Logger, resolvers []string) (err error) {
|
||||
var resolvconf strings.Builder
|
||||
|
||||
for idx, resolver := range resolvers {
|
||||
// Only allow the first 3 nameservers since that is all that will be used
|
||||
if idx >= 3 {
|
||||
break
|
||||
}
|
||||
|
||||
if _, err = resolvconf.WriteString(fmt.Sprintf("nameserver %s\n", resolver)); err != nil {
|
||||
logger.Println("failed to add some resolver to resolvconf:", resolver)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if domain, err := talosnet.DomainName(); err == nil {
|
||||
if domain != "" {
|
||||
if _, err = resolvconf.WriteString(fmt.Sprintf("search %s\n", domain)); err != nil {
|
||||
return fmt.Errorf("failed to add domain search line to resolvconf: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println("writing resolvconf")
|
||||
|
||||
return ioutil.WriteFile("/etc/resolv.conf", []byte(resolvconf.String()), 0o644)
|
||||
}
|
||||
|
||||
const hostsTemplate = `
|
||||
127.0.0.1 localhost
|
||||
{{ .IP }} {{ .Hostname }} {{ if ne .Hostname .Alias }}{{ .Alias }}{{ end }}
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
|
||||
{{ with .ExtraHosts }}
|
||||
{{ range . }}
|
||||
{{ .IP }} {{ range .Aliases }}{{.}} {{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
`
|
||||
|
||||
func writeHosts(hostname string, address net.IP, cfg config.Provider) (err error) {
|
||||
extraHosts := []config.ExtraHost{}
|
||||
|
||||
if cfg != nil {
|
||||
extraHosts = cfg.Machine().Network().ExtraHosts()
|
||||
}
|
||||
|
||||
data := struct {
|
||||
IP string
|
||||
Hostname string
|
||||
Alias string
|
||||
ExtraHosts []config.ExtraHost
|
||||
}{
|
||||
IP: address.String(),
|
||||
Hostname: hostname,
|
||||
Alias: strings.Split(hostname, ".")[0],
|
||||
ExtraHosts: extraHosts,
|
||||
}
|
||||
|
||||
var tmpl *template.Template
|
||||
|
||||
tmpl, err = template.New("").Parse(hostsTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
|
||||
writer := bytes.NewBuffer(buf)
|
||||
|
||||
err = tmpl.Execute(writer, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile("/etc/hosts", writer.Bytes(), 0o644)
|
||||
}
|
@ -1,323 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package networkd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// buildOptions translates the supplied config to nic.Option used for
|
||||
// configuring the interface.
|
||||
//nolint:gocyclo,cyclop
|
||||
func buildOptions(logger *log.Logger, device config.Device, hostname string) (name string, opts []nic.Option, err error) {
|
||||
opts = append(opts, nic.WithName(device.Interface()))
|
||||
|
||||
if device.Ignore() || procfs.ProcCmdline().Get(constants.KernelParamNetworkInterfaceIgnore).Contains(device.Interface()) {
|
||||
opts = append(opts, nic.WithIgnore())
|
||||
|
||||
return device.Interface(), opts, err
|
||||
}
|
||||
|
||||
// Configure Addressing
|
||||
switch {
|
||||
case device.CIDR() != "":
|
||||
s := &address.Static{CIDR: device.CIDR(), RouteList: device.Routes(), Mtu: device.MTU()}
|
||||
|
||||
// Set a default for the hostname to ensure we always have a valid
|
||||
// ip + hostname pair
|
||||
ip := s.Address().IP.String()
|
||||
s.FQDN = fmt.Sprintf("%s-%s", "talos", strings.ReplaceAll(ip, ".", "-"))
|
||||
|
||||
if hostname != "" {
|
||||
s.FQDN = hostname
|
||||
}
|
||||
|
||||
opts = append(opts, nic.WithAddressing(s))
|
||||
case device.DHCP():
|
||||
if device.DHCPOptions().IPv4() {
|
||||
d := &address.DHCP4{DHCPOptions: device.DHCPOptions(), RouteList: device.Routes(), Mtu: device.MTU()}
|
||||
opts = append(opts, nic.WithAddressing(d))
|
||||
}
|
||||
|
||||
if device.DHCPOptions().IPv6() {
|
||||
d := &address.DHCP6{Mtu: device.MTU()}
|
||||
opts = append(opts, nic.WithAddressing(d))
|
||||
}
|
||||
default:
|
||||
// Allow master interface without any addressing if VLANs exist
|
||||
if len(device.Vlans()) > 0 {
|
||||
logger.Printf("no addressing for master device %s", device.Interface())
|
||||
|
||||
opts = append(opts, nic.WithNoAddressing())
|
||||
} else {
|
||||
// No CIDR and DHCP==false results in a static without an IP.
|
||||
// This handles cases like slaac addressing.
|
||||
s := &address.Static{RouteList: device.Routes(), Mtu: device.MTU()}
|
||||
opts = append(opts, nic.WithAddressing(s))
|
||||
}
|
||||
}
|
||||
|
||||
// Configure Vlan interfaces
|
||||
for _, vlan := range device.Vlans() {
|
||||
opts = append(opts, nic.WithVlan(vlan.ID()))
|
||||
if vlan.CIDR() != "" {
|
||||
opts = append(opts, nic.WithVlanCIDR(vlan.ID(), vlan.CIDR(), vlan.Routes()))
|
||||
}
|
||||
|
||||
if vlan.DHCP() {
|
||||
opts = append(opts, nic.WithVlanDhcp(vlan.ID()))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle dummy interface
|
||||
if device.Dummy() {
|
||||
opts = append(opts, nic.WithDummy())
|
||||
}
|
||||
|
||||
if device.WireguardConfig() != nil {
|
||||
opts = append(opts, nic.WithWireguardConfig(device.WireguardConfig()))
|
||||
}
|
||||
|
||||
if device.VIPConfig() != nil {
|
||||
opts = append(opts, nic.WithVIPConfig(device.VIPConfig()))
|
||||
}
|
||||
|
||||
// Configure Bonding
|
||||
if device.Bond() == nil {
|
||||
return device.Interface(), opts, err
|
||||
}
|
||||
|
||||
opts = append(opts, nic.WithBond(true))
|
||||
|
||||
if len(device.Bond().Interfaces()) == 0 {
|
||||
return device.Interface(), opts, fmt.Errorf("invalid bond configuration for %s: must supply sub interfaces for bonded interface", device.Interface())
|
||||
}
|
||||
|
||||
opts = append(opts, nic.WithSubInterface(device.Bond().Interfaces()...))
|
||||
|
||||
if device.Bond().Mode() != "" {
|
||||
opts = append(opts, nic.WithBondMode(device.Bond().Mode()))
|
||||
}
|
||||
|
||||
if device.Bond().HashPolicy() != "" {
|
||||
opts = append(opts, nic.WithHashPolicy(device.Bond().HashPolicy()))
|
||||
}
|
||||
|
||||
if device.Bond().LACPRate() != "" {
|
||||
opts = append(opts, nic.WithLACPRate(device.Bond().LACPRate()))
|
||||
}
|
||||
|
||||
if device.Bond().MIIMon() > 0 {
|
||||
opts = append(opts, nic.WithMIIMon(device.Bond().MIIMon()))
|
||||
}
|
||||
|
||||
if device.Bond().UpDelay() > 0 {
|
||||
opts = append(opts, nic.WithUpDelay(device.Bond().UpDelay()))
|
||||
}
|
||||
|
||||
if device.Bond().DownDelay() > 0 {
|
||||
opts = append(opts, nic.WithDownDelay(device.Bond().DownDelay()))
|
||||
}
|
||||
|
||||
if !device.Bond().UseCarrier() {
|
||||
opts = append(opts, nic.WithUseCarrier(device.Bond().UseCarrier()))
|
||||
}
|
||||
|
||||
if device.Bond().ARPInterval() > 0 {
|
||||
opts = append(opts, nic.WithARPInterval(device.Bond().ARPInterval()))
|
||||
}
|
||||
|
||||
// if device.Bond.ARPIPTarget {
|
||||
// opts = append(opts, nic.WithARPIPTarget(device.Bond.ARPIPTarget))
|
||||
//}
|
||||
|
||||
if device.Bond().ARPValidate() != "" {
|
||||
opts = append(opts, nic.WithARPValidate(device.Bond().ARPValidate()))
|
||||
}
|
||||
|
||||
if device.Bond().ARPAllTargets() != "" {
|
||||
opts = append(opts, nic.WithARPAllTargets(device.Bond().ARPAllTargets()))
|
||||
}
|
||||
|
||||
if device.Bond().Primary() != "" {
|
||||
opts = append(opts, nic.WithPrimary(device.Bond().Primary()))
|
||||
}
|
||||
|
||||
if device.Bond().PrimaryReselect() != "" {
|
||||
opts = append(opts, nic.WithPrimaryReselect(device.Bond().PrimaryReselect()))
|
||||
}
|
||||
|
||||
if device.Bond().FailOverMac() != "" {
|
||||
opts = append(opts, nic.WithFailOverMAC(device.Bond().FailOverMac()))
|
||||
}
|
||||
|
||||
if device.Bond().ResendIGMP() > 0 {
|
||||
opts = append(opts, nic.WithResendIGMP(device.Bond().ResendIGMP()))
|
||||
}
|
||||
|
||||
if device.Bond().NumPeerNotif() > 0 {
|
||||
opts = append(opts, nic.WithNumPeerNotif(device.Bond().NumPeerNotif()))
|
||||
}
|
||||
|
||||
if device.Bond().AllSlavesActive() > 0 {
|
||||
opts = append(opts, nic.WithAllSlavesActive(device.Bond().AllSlavesActive()))
|
||||
}
|
||||
|
||||
if device.Bond().MinLinks() > 0 {
|
||||
opts = append(opts, nic.WithMinLinks(device.Bond().MinLinks()))
|
||||
}
|
||||
|
||||
if device.Bond().LPInterval() > 0 {
|
||||
opts = append(opts, nic.WithLPInterval(device.Bond().LPInterval()))
|
||||
}
|
||||
|
||||
if device.Bond().PacketsPerSlave() > 0 {
|
||||
opts = append(opts, nic.WithPacketsPerSlave(device.Bond().PacketsPerSlave()))
|
||||
}
|
||||
|
||||
if device.Bond().ADSelect() != "" {
|
||||
opts = append(opts, nic.WithADSelect(device.Bond().ADSelect()))
|
||||
}
|
||||
|
||||
if device.Bond().ADActorSysPrio() > 0 {
|
||||
opts = append(opts, nic.WithADActorSysPrio(device.Bond().ADActorSysPrio()))
|
||||
}
|
||||
|
||||
if device.Bond().ADUserPortKey() > 0 {
|
||||
opts = append(opts, nic.WithADUserPortKey(device.Bond().ADUserPortKey()))
|
||||
}
|
||||
|
||||
// if device.Bond.ADActorSystem != "" {
|
||||
// opts = append(opts, nic.WithADActorSystem(device.Bond.ADActorSystem))
|
||||
//}
|
||||
|
||||
if device.Bond().TLBDynamicLB() > 0 {
|
||||
opts = append(opts, nic.WithTLBDynamicLB(device.Bond().TLBDynamicLB()))
|
||||
}
|
||||
|
||||
if device.Bond().PeerNotifyDelay() > 0 {
|
||||
opts = append(opts, nic.WithPeerNotifyDelay(device.Bond().PeerNotifyDelay()))
|
||||
}
|
||||
|
||||
return device.Interface(), opts, err
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func buildKernelOptions(cmdline string) (name string, opts []nic.Option) {
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt
|
||||
// ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>:<ntp0-ip>
|
||||
fields := strings.Split(cmdline, ":")
|
||||
|
||||
// If dhcp is specified, we'll handle it as a normal discovered
|
||||
// interface
|
||||
if len(fields) == 1 && fields[0] == "dhcp" {
|
||||
return name, opts
|
||||
}
|
||||
|
||||
// If there are not enough fields specified, we'll bail
|
||||
if len(fields) < 4 {
|
||||
return name, opts
|
||||
}
|
||||
|
||||
var (
|
||||
device = &v1alpha1.Device{}
|
||||
hostname string
|
||||
link *net.Interface
|
||||
resolvers = []net.IP{}
|
||||
)
|
||||
|
||||
for idx, field := range fields {
|
||||
switch idx {
|
||||
// Address
|
||||
case 0:
|
||||
device.DeviceCIDR = field
|
||||
// NFS Server
|
||||
// case 1:
|
||||
// Gateway
|
||||
case 2:
|
||||
device.DeviceRoutes = []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "0.0.0.0/0",
|
||||
RouteGateway: field,
|
||||
},
|
||||
}
|
||||
// Netmask
|
||||
case 3:
|
||||
mask := net.ParseIP(field).To4()
|
||||
ipmask := net.IPv4Mask(mask[0], mask[1], mask[2], mask[3])
|
||||
ones, _ := ipmask.Size()
|
||||
device.DeviceCIDR = fmt.Sprintf("%s/%d", device.CIDR(), ones)
|
||||
// Hostname
|
||||
case 4:
|
||||
hostname = field
|
||||
// Interface name
|
||||
case 5:
|
||||
iface, err := net.InterfaceByName(field)
|
||||
if err == nil {
|
||||
link = iface
|
||||
}
|
||||
// Configuration method
|
||||
// case 6:
|
||||
// Primary DNS Resolver
|
||||
case 7:
|
||||
fallthrough
|
||||
// Secondary DNS Resolver
|
||||
case 8:
|
||||
nameserverIP := net.ParseIP(field)
|
||||
if nameserverIP != nil {
|
||||
resolvers = append(resolvers, nameserverIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
// NTP server
|
||||
// case 9:
|
||||
// // k.NTPServer = field
|
||||
|
||||
// Find the first non-loopback interface
|
||||
if link == nil {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return hostname, opts
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
i := iface
|
||||
|
||||
link = &i
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if device.DeviceInterface == "" {
|
||||
opts = append(opts, nic.WithName(link.Name))
|
||||
}
|
||||
|
||||
routes := make([]config.Route, len(device.DeviceRoutes))
|
||||
|
||||
for i := 0; i < len(device.DeviceRoutes); i++ {
|
||||
routes[i] = device.DeviceRoutes[i]
|
||||
}
|
||||
|
||||
s := &address.Static{Mtu: device.DeviceMTU, NameServers: resolvers, FQDN: hostname, NetIf: link, CIDR: device.DeviceCIDR, RouteList: routes}
|
||||
opts = append(opts, nic.WithAddressing(s))
|
||||
|
||||
return link.Name, opts
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//nolint:testpackage
|
||||
package networkd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type NetconfSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestNetconfSuite(t *testing.T) {
|
||||
// Hide all our state transition messages
|
||||
// log.SetOutput(ioutil.Discard)
|
||||
suite.Run(t, new(NetconfSuite))
|
||||
}
|
||||
|
||||
func (suite *NetconfSuite) TestBaseNetconf() {
|
||||
for _, device := range sampleConfig() {
|
||||
_, opts, err := buildOptions(log.New(os.Stderr, "", log.LstdFlags), device, "")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
_, err = nic.New(opts...)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *NetconfSuite) TestKernelNetconf() {
|
||||
name, opts := buildKernelOptions(sampleKernelIPParam())
|
||||
|
||||
iface, err := nic.New(opts...)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().Equal(iface.Name, name)
|
||||
suite.Assert().Equal(len(iface.AddressMethod), 1)
|
||||
addr := iface.AddressMethod[0]
|
||||
suite.Assert().Equal(addr.Name(), "static")
|
||||
suite.Assert().Equal(addr.Hostname(), "hostname")
|
||||
suite.Assert().Equal(addr.Address().IP, net.ParseIP("1.1.1.1"))
|
||||
suite.Assert().Equal(len(addr.Resolvers()), 2)
|
||||
suite.Assert().Equal(addr.Resolvers()[0], net.ParseIP("4.4.4.4"))
|
||||
suite.Assert().Equal(addr.Resolvers()[1], net.ParseIP("5.5.5.5"))
|
||||
suite.Assert().Equal(len(addr.Routes()), 1)
|
||||
}
|
||||
|
||||
func (suite *NetconfSuite) TestKernelNetconfIncomplete() {
|
||||
name, opts := buildKernelOptions("1.1.1.1::3.3.3.3:255.255.255.0::eth0:none:::")
|
||||
|
||||
iface, err := nic.New(opts...)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().Equal(iface.Name, name)
|
||||
suite.Assert().Equal(len(iface.AddressMethod), 1)
|
||||
addr := iface.AddressMethod[0]
|
||||
suite.Assert().Equal(addr.Name(), "static")
|
||||
suite.Assert().Equal(addr.Hostname(), "")
|
||||
suite.Assert().Equal(addr.Address().IP, net.ParseIP("1.1.1.1"))
|
||||
suite.Assert().Len(addr.Resolvers(), 0)
|
||||
suite.Assert().Equal(len(addr.Routes()), 1)
|
||||
}
|
||||
|
||||
func sampleConfig() []config.Device {
|
||||
return []config.Device{
|
||||
&v1alpha1.Device{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceCIDR: "192.168.0.10/24",
|
||||
},
|
||||
&v1alpha1.Device{
|
||||
DeviceInterface: "bond0",
|
||||
DeviceCIDR: "192.168.0.10/24",
|
||||
DeviceBond: &v1alpha1.Bond{BondInterfaces: []string{"lo"}},
|
||||
},
|
||||
&v1alpha1.Device{
|
||||
DeviceInterface: "bond0",
|
||||
DeviceBond: &v1alpha1.Bond{BondInterfaces: []string{"lo"}, BondMode: "balance-rr"},
|
||||
},
|
||||
&v1alpha1.Device{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceIgnore: true,
|
||||
},
|
||||
&v1alpha1.Device{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceMTU: 9100,
|
||||
DeviceCIDR: "192.168.0.10/24",
|
||||
DeviceRoutes: []*v1alpha1.Route{{RouteNetwork: "10.0.0.0/8", RouteGateway: "10.0.0.1"}},
|
||||
},
|
||||
&v1alpha1.Device{
|
||||
DeviceInterface: "bond0",
|
||||
DeviceBond: &v1alpha1.Bond{
|
||||
BondInterfaces: []string{"lo"},
|
||||
BondMode: "balance-rr",
|
||||
BondHashPolicy: "layer2",
|
||||
BondLACPRate: "fast",
|
||||
BondMIIMon: 200,
|
||||
BondUpDelay: 100,
|
||||
BondDownDelay: 100,
|
||||
},
|
||||
},
|
||||
&v1alpha1.Device{
|
||||
DeviceInterface: "bondyolo0",
|
||||
DeviceBond: &v1alpha1.Bond{
|
||||
BondInterfaces: []string{"lo"},
|
||||
BondMode: "balance-rr",
|
||||
BondHashPolicy: "layer2",
|
||||
BondLACPRate: "fast",
|
||||
BondMIIMon: 200,
|
||||
BondUpDelay: 100,
|
||||
BondDownDelay: 100,
|
||||
BondUseCarrier: nil,
|
||||
BondARPInterval: 230,
|
||||
BondARPValidate: "all",
|
||||
BondARPAllTargets: "all",
|
||||
BondPrimary: "lo",
|
||||
BondPrimaryReselect: "better",
|
||||
BondFailOverMac: "none",
|
||||
BondResendIGMP: 10,
|
||||
BondNumPeerNotif: 5,
|
||||
BondAllSlavesActive: 1,
|
||||
BondMinLinks: 1,
|
||||
BondLPInterval: 100,
|
||||
BondPacketsPerSlave: 50,
|
||||
BondADSelect: "bandwidth",
|
||||
BondADActorSysPrio: 23,
|
||||
BondADUserPortKey: 323,
|
||||
BondTLBDynamicLB: 1,
|
||||
BondPeerNotifyDelay: 200,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func sampleKernelIPParam() string {
|
||||
return "1.1.1.1:2.2.2.2:3.3.3.3:255.255.255.0:hostname:eth0:none:4.4.4.4:5.5.5.5:6.6.6.6"
|
||||
}
|
@ -1,468 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package networkd handles the network interface configuration on a host.
|
||||
// If no configuration is provided, automatic configuration via dhcp will
|
||||
// be performed on interfaces ( eth, en, bond ).
|
||||
package networkd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// Set up default nameservers.
|
||||
const (
|
||||
DefaultPrimaryResolver = "1.1.1.1"
|
||||
DefaultSecondaryResolver = "8.8.8.8"
|
||||
)
|
||||
|
||||
// Networkd provides the high level interaction to configure network interfaces
|
||||
// on a host system. This currently supports addressing configuration via dhcp
|
||||
// and/or a specified configuration file.
|
||||
type Networkd struct {
|
||||
Interfaces map[string]*nic.NetworkInterface
|
||||
Config config.Provider
|
||||
|
||||
hostname string
|
||||
resolvers []string
|
||||
|
||||
sync.Mutex
|
||||
ready bool
|
||||
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// New takes the supplied configuration and creates an abstract representation
|
||||
// of all interfaces (as nic.NetworkInterface).
|
||||
//nolint:gocyclo,cyclop
|
||||
func New(logger *log.Logger, config config.Provider) (*Networkd, error) {
|
||||
var (
|
||||
hostname string
|
||||
option *string
|
||||
result *multierror.Error
|
||||
resolvers []string
|
||||
)
|
||||
|
||||
netconf := make(map[string][]nic.Option)
|
||||
|
||||
if option = procfs.ProcCmdline().Get("ip").First(); option != nil {
|
||||
if name, opts := buildKernelOptions(*option); name != "" {
|
||||
netconf[name] = opts
|
||||
}
|
||||
}
|
||||
|
||||
// Gather settings for all config driven interfaces
|
||||
if config != nil {
|
||||
logger.Println("parsing configuration file")
|
||||
|
||||
for _, device := range config.Machine().Network().Devices() {
|
||||
name, opts, err := buildOptions(logger, device, config.Machine().Network().Hostname())
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := netconf[name]; ok {
|
||||
netconf[name] = append(netconf[name], opts...)
|
||||
} else {
|
||||
netconf[name] = opts
|
||||
}
|
||||
}
|
||||
|
||||
hostname = config.Machine().Network().Hostname()
|
||||
|
||||
if len(config.Machine().Network().Resolvers()) > 0 {
|
||||
resolvers = config.Machine().Network().Resolvers()
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println("discovering local interfaces")
|
||||
|
||||
// Gather already present interfaces
|
||||
localInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
|
||||
return &Networkd{}, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// Add locally discovered interfaces to our list of interfaces
|
||||
// if they are not already present
|
||||
filtered, err := filterInterfaces(logger, localInterfaces)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
|
||||
return &Networkd{}, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
for _, device := range filtered {
|
||||
if _, ok := netconf[device.Name]; !ok {
|
||||
netconf[device.Name] = []nic.Option{nic.WithName(device.Name)}
|
||||
|
||||
// Explicitly ignore bonded interfaces if no configuration was specified
|
||||
// This should speed up initial boot times since an unconfigured bond
|
||||
// does not provide any value.
|
||||
if strings.HasPrefix(device.Name, "bond") {
|
||||
netconf[device.Name] = append(netconf[device.Name], nic.WithIgnore())
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure lo has proper loopback address
|
||||
// Ensure MTU for loopback is 64k
|
||||
// ref: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0cf833aefaa85bbfce3ff70485e5534e09254773
|
||||
if strings.HasPrefix(device.Name, "lo") {
|
||||
netconf[device.Name] = append(netconf[device.Name], nic.WithAddressing(
|
||||
&address.Static{
|
||||
CIDR: "127.0.0.1/8",
|
||||
Mtu: nic.MaximumMTU,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// add local interfaces which were filtered out with Ignore
|
||||
for _, device := range localInterfaces {
|
||||
if _, ok := netconf[device.Name]; !ok {
|
||||
netconf[device.Name] = []nic.Option{nic.WithName(device.Name), nic.WithIgnore()}
|
||||
}
|
||||
}
|
||||
|
||||
interfaces := make(map[string]*nic.NetworkInterface)
|
||||
|
||||
// Create nic.NetworkInterface representation of the interface
|
||||
for ifname, opts := range netconf {
|
||||
netif, err := nic.New(opts...)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
interfaces[ifname] = netif
|
||||
}
|
||||
|
||||
// Set interfaces that are part of a bond to ignored
|
||||
for _, netif := range interfaces {
|
||||
if !netif.Bonded {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, subif := range netif.SubInterfaces {
|
||||
if _, ok := interfaces[subif.Name]; !ok {
|
||||
result = multierror.Append(result, fmt.Errorf("bond subinterface %s does not exist", subif.Name))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
interfaces[subif.Name].Ignore = true
|
||||
}
|
||||
}
|
||||
|
||||
return &Networkd{
|
||||
Interfaces: interfaces,
|
||||
Config: config,
|
||||
hostname: hostname,
|
||||
resolvers: resolvers,
|
||||
logger: logger,
|
||||
}, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// Configure handles the lifecycle for an interface. This includes creation,
|
||||
// configuration, and any addressing that is needed. We care about ordering
|
||||
// here so that we can ensure any links that make up a bond will be in
|
||||
// the correct state when we get to bonding configuration.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (n *Networkd) Configure(ctx context.Context) (err error) {
|
||||
// Configure non-bonded interfaces first so we can ensure basic
|
||||
// interfaces exist prior to bonding
|
||||
for _, bonded := range []bool{false, true} {
|
||||
if bonded {
|
||||
n.logger.Println("configuring bonded interfaces")
|
||||
} else {
|
||||
n.logger.Println("configuring non-bonded interfaces")
|
||||
}
|
||||
|
||||
if err = n.configureLinks(ctx, bonded); err != nil {
|
||||
// Treat errors as non-fatal
|
||||
n.logger.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
// prefer resolvers from the configuration
|
||||
resolvers := append([]string(nil), n.resolvers...)
|
||||
|
||||
// if no resolvers configured, use addressing method resolvers
|
||||
if len(resolvers) == 0 {
|
||||
for _, netif := range n.Interfaces {
|
||||
for _, method := range netif.AddressMethod {
|
||||
if !method.Valid() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, resolver := range method.Resolvers() {
|
||||
resolvers = append(resolvers, resolver.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use default resolvers if nothing is configured
|
||||
if len(resolvers) == 0 {
|
||||
resolvers = append(resolvers, DefaultPrimaryResolver, DefaultSecondaryResolver)
|
||||
}
|
||||
|
||||
// Set hostname must be before the resolv configuration
|
||||
// so we can ensure the hosts domainname is set properly
|
||||
// before we write the search stanza
|
||||
if err = n.Hostname(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = writeResolvConf(n.logger, resolvers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.SetReady()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Renew sets up a long running loop to refresh a network interfaces
|
||||
// addressing configuration. Currently this only applies to interfaces
|
||||
// configured by DHCP.
|
||||
func (n *Networkd) Renew(ctx context.Context) {
|
||||
for _, iface := range n.Interfaces {
|
||||
iface.Renew(ctx, n.logger)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset handles removing addresses from previously configured interfaces.
|
||||
func (n *Networkd) Reset() {
|
||||
for _, iface := range n.Interfaces {
|
||||
iface.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// RunControllers spins up additional controllers in the errgroup.
|
||||
func (n *Networkd) RunControllers(ctx context.Context, eg *errgroup.Group) error {
|
||||
for _, iface := range n.Interfaces {
|
||||
if err := iface.RunControllers(ctx, n.logger, eg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hostname returns the first hostname found from the addressing methods.
|
||||
// Create /etc/hosts and set hostname.
|
||||
// Priority is:
|
||||
// 1. Config (explicitly defined by the user)
|
||||
// 2. Kernel arg
|
||||
// 3. Platform
|
||||
// 4. DHCP
|
||||
// 5. Default with the format: talos-<ip addr>.
|
||||
func (n *Networkd) Hostname() (err error) {
|
||||
hostname, domainname, address, err := n.decideHostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = writeHosts(hostname, address, n.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var p runtime.Platform
|
||||
|
||||
p, err = platform.CurrentPlatform()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip hostname/domainname setting when running in container mode
|
||||
if p.Mode() == runtime.ModeContainer {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = unix.Sethostname([]byte(hostname)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unix.Setdomainname([]byte(domainname))
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *Networkd) decideHostname() (hostname, domainname string, address net.IP, err error) {
|
||||
// Set hostname to default
|
||||
address = net.ParseIP("127.0.1.1")
|
||||
hostname = fmt.Sprintf("%s-%s", "talos", strings.ReplaceAll(address.String(), ".", "-"))
|
||||
|
||||
// Sort interface names alphabetically so we can ensure parsing order
|
||||
interfaceNames := make([]string, 0, len(n.Interfaces))
|
||||
for intName := range n.Interfaces {
|
||||
interfaceNames = append(interfaceNames, intName)
|
||||
}
|
||||
|
||||
sort.Strings(interfaceNames)
|
||||
|
||||
// Loop through address responses and use the first hostname
|
||||
// and address response.
|
||||
outer:
|
||||
for _, intName := range interfaceNames {
|
||||
iface := n.Interfaces[intName]
|
||||
|
||||
// Skip loopback interface because it will always have
|
||||
// a hardcoded hostname of `talos-ip`
|
||||
if iface.Link != nil && iface.Link.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, method := range iface.AddressMethod {
|
||||
if !method.Valid() {
|
||||
continue
|
||||
}
|
||||
|
||||
if method.Hostname() != "" {
|
||||
hostname = method.Hostname()
|
||||
|
||||
address = method.Address().IP
|
||||
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Platform
|
||||
var p runtime.Platform
|
||||
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer ctxCancel()
|
||||
|
||||
p, err = platform.CurrentPlatform()
|
||||
if err == nil {
|
||||
var pHostname []byte
|
||||
|
||||
if pHostname, err = p.Hostname(ctx); err == nil && string(pHostname) != "" {
|
||||
hostname = string(pHostname)
|
||||
}
|
||||
}
|
||||
|
||||
// Kernel
|
||||
if kernelHostname := procfs.ProcCmdline().Get(constants.KernelParamHostname).First(); kernelHostname != nil {
|
||||
hostname = *kernelHostname
|
||||
}
|
||||
|
||||
// Allow user supplied hostname to win
|
||||
if n.hostname != "" {
|
||||
hostname = n.hostname
|
||||
}
|
||||
|
||||
hostParts := strings.Split(hostname, ".")
|
||||
|
||||
if len(hostParts[0]) > 63 {
|
||||
return "", "", net.IP{}, fmt.Errorf("hostname length longer than max allowed (63): %s", hostParts[0])
|
||||
}
|
||||
|
||||
if len(hostname) > 253 {
|
||||
return "", "", net.IP{}, fmt.Errorf("hostname fqdn length longer than max allowed (253): %s", hostname)
|
||||
}
|
||||
|
||||
hostname = hostParts[0]
|
||||
|
||||
if len(hostParts) > 1 {
|
||||
domainname = strings.Join(hostParts[1:], ".")
|
||||
}
|
||||
|
||||
// Only return the hostname portion of the name ( strip domain bits off )
|
||||
return hostname, domainname, address, nil
|
||||
}
|
||||
|
||||
// Ready exposes the readiness state of networkd.
|
||||
func (n *Networkd) Ready() bool {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.ready
|
||||
}
|
||||
|
||||
// SetReady sets the readiness state of networkd.
|
||||
func (n *Networkd) SetReady() {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
n.ready = true
|
||||
}
|
||||
|
||||
func (n *Networkd) configureLinks(ctx context.Context, bonded bool) error {
|
||||
errCh := make(chan error, len(n.Interfaces))
|
||||
count := 0
|
||||
|
||||
for _, iface := range n.Interfaces {
|
||||
if iface.Bonded != bonded {
|
||||
continue
|
||||
}
|
||||
|
||||
count++
|
||||
|
||||
go func(netif *nic.NetworkInterface) {
|
||||
if !netif.IsIgnored() {
|
||||
n.logger.Printf("setting up %s", netif.Name)
|
||||
}
|
||||
|
||||
errCh <- func() error {
|
||||
// Ensure link exists
|
||||
if err := netif.Create(); err != nil {
|
||||
return fmt.Errorf("error creating nic %q: %w", netif.Name, err)
|
||||
}
|
||||
|
||||
if err := netif.CreateSub(n.logger); err != nil {
|
||||
return fmt.Errorf("error creating sub interface nic %q: %w", netif.Name, err)
|
||||
}
|
||||
|
||||
if err := netif.Configure(ctx); err != nil {
|
||||
return fmt.Errorf("error configuring nic %q: %w", netif.Name, err)
|
||||
}
|
||||
|
||||
if err := netif.Addressing(n.logger); err != nil {
|
||||
return fmt.Errorf("error configuring addressing %q: %w", netif.Name, err)
|
||||
}
|
||||
|
||||
if err := netif.AddressingSub(n.logger); err != nil {
|
||||
return fmt.Errorf("error configuring addressing %q: %w", netif.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
}(iface)
|
||||
}
|
||||
|
||||
var multiErr *multierror.Error
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
multiErr = multierror.Append(multiErr, <-errCh)
|
||||
}
|
||||
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
@ -1,213 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//nolint:testpackage
|
||||
package networkd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type NetworkdSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestNetworkdSuite(t *testing.T) {
|
||||
// Hide all our state transition messages
|
||||
// log.SetOutput(ioutil.Discard)
|
||||
suite.Run(t, new(NetworkdSuite))
|
||||
}
|
||||
|
||||
func (suite *NetworkdSuite) TestNetworkd() {
|
||||
nwd, err := New(log.New(os.Stderr, "", log.LstdFlags), sampleConfigFile())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().Contains(nwd.Interfaces, "eth0")
|
||||
suite.Assert().False(nwd.Interfaces["eth0"].Bonded)
|
||||
suite.Require().Contains(nwd.Interfaces, "bond0")
|
||||
suite.Assert().True(nwd.Interfaces["bond0"].Bonded)
|
||||
suite.Assert().Equal(1, len(nwd.Interfaces["bond0"].SubInterfaces))
|
||||
suite.Require().Contains(nwd.Interfaces, "lo")
|
||||
}
|
||||
|
||||
func (suite *NetworkdSuite) TestHostname() {
|
||||
var (
|
||||
addr net.IP
|
||||
domainname string
|
||||
err error
|
||||
hostname string
|
||||
nwd *Networkd
|
||||
sampleConfig config.Provider
|
||||
)
|
||||
|
||||
nwd, err = New(log.New(os.Stderr, "", log.LstdFlags), nil)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Default test
|
||||
hostname, _, addr, err = nwd.decideHostname()
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal("talos-127-0-1-1", hostname)
|
||||
suite.Assert().Equal(addr, net.ParseIP("127.0.1.1"))
|
||||
|
||||
// Static addressing tests
|
||||
|
||||
// Static with hostname
|
||||
sampleConfig = sampleConfigFile()
|
||||
|
||||
nwd, err = New(log.New(os.Stderr, "", log.LstdFlags), sampleConfig)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
hostname, _, addr, err = nwd.decideHostname()
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal("myhostname", hostname)
|
||||
suite.Assert().Equal(addr, net.ParseIP("192.168.0.10"))
|
||||
|
||||
// Static for computed hostname ( talos-ip )
|
||||
sampleConfig.(*v1alpha1.Config).MachineConfig.MachineNetwork.NetworkHostname = ""
|
||||
|
||||
nwd, err = New(log.New(os.Stderr, "", log.LstdFlags), sampleConfig)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
hostname, _, addr, err = nwd.decideHostname()
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal("talos-192-168-0-10", hostname)
|
||||
suite.Assert().Equal(addr, net.ParseIP("192.168.0.10"))
|
||||
|
||||
// Static for hostname too long
|
||||
sampleConfig.(*v1alpha1.Config).MachineConfig.MachineNetwork.NetworkHostname = "somereallyreallyreallylongstringthathasmorethan63charactersbecauseweneedtotestit"
|
||||
|
||||
nwd, err = New(log.New(os.Stderr, "", log.LstdFlags), sampleConfig)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
//nolint:dogsled
|
||||
_, _, _, err = nwd.decideHostname()
|
||||
suite.Require().Error(err)
|
||||
|
||||
// Static for hostname vs domain name
|
||||
sampleConfig.(*v1alpha1.Config).MachineConfig.MachineNetwork.NetworkHostname = "dadjokes.biz.dev.com.org.io"
|
||||
|
||||
nwd, err = New(log.New(os.Stderr, "", log.LstdFlags), sampleConfig)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
hostname, domainname, _, err = nwd.decideHostname()
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal("dadjokes", hostname)
|
||||
suite.Assert().Equal("biz.dev.com.org.io", domainname)
|
||||
|
||||
// DHCP addressing tests
|
||||
|
||||
// DHCP with OptionHostName
|
||||
nwd, err = New(log.New(os.Stderr, "", log.LstdFlags), dhcpConfigFile())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
nwd.Interfaces["eth0"].AddressMethod = []address.Addressing{
|
||||
&address.DHCP4{
|
||||
Ack: &dhcpv4.DHCPv4{
|
||||
YourIPAddr: net.ParseIP("192.168.0.11"),
|
||||
Options: dhcpv4.Options{
|
||||
uint8(dhcpv4.OptionHostName): []byte("evenbetterdadjokes"),
|
||||
uint8(dhcpv4.OptionSubnetMask): []byte{255, 255, 255, 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hostname, _, addr, err = nwd.decideHostname()
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal("evenbetterdadjokes", hostname)
|
||||
suite.Assert().Equal(addr.String(), "192.168.0.11")
|
||||
|
||||
// DHCP without OptionHostName
|
||||
nwd, err = New(log.New(os.Stderr, "", log.LstdFlags), dhcpConfigFile())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
nwd.Interfaces["eth0"].AddressMethod = []address.Addressing{
|
||||
&address.DHCP4{
|
||||
Ack: &dhcpv4.DHCPv4{
|
||||
YourIPAddr: net.ParseIP("192.168.0.11"),
|
||||
Options: dhcpv4.Options{
|
||||
uint8(dhcpv4.OptionSubnetMask): []byte{255, 255, 255, 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hostname, _, addr, err = nwd.decideHostname()
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal("talos-192-168-0-11", hostname)
|
||||
suite.Assert().Equal(addr, net.ParseIP("192.168.0.11"))
|
||||
|
||||
// DHCP without OptionHostname and with OptionDomainName
|
||||
nwd.Interfaces["eth0"].AddressMethod = []address.Addressing{
|
||||
&address.DHCP4{
|
||||
Ack: &dhcpv4.DHCPv4{
|
||||
YourIPAddr: net.ParseIP("192.168.0.11"),
|
||||
Options: dhcpv4.Options{
|
||||
uint8(dhcpv4.OptionDomainName): []byte("domain.tld"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hostname, domainname, addr, err = nwd.decideHostname()
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal("talos-192-168-0-11", hostname)
|
||||
suite.Assert().Equal("domain.tld", domainname)
|
||||
suite.Assert().Equal(addr, net.ParseIP("192.168.0.11"))
|
||||
}
|
||||
|
||||
func sampleConfigFile() config.Provider {
|
||||
return &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NameServers: []string{"1.2.3.4", "2.3.4.5"},
|
||||
NetworkHostname: "myhostname",
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceCIDR: "192.168.0.10/24",
|
||||
DeviceMTU: 9100,
|
||||
},
|
||||
{
|
||||
DeviceInterface: "bond0",
|
||||
DeviceCIDR: "192.168.0.10/24",
|
||||
DeviceBond: &v1alpha1.Bond{
|
||||
BondInterfaces: []string{"lo"},
|
||||
BondMode: "balance-rr",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dhcpConfigFile() config.Provider {
|
||||
return &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
},
|
||||
{
|
||||
DeviceInterface: "eth1",
|
||||
DeviceCIDR: "192.168.0.10/24",
|
||||
DeviceMTU: 9100,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -1,373 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Additional information can be found
|
||||
// https://www.kernel.org/doc/Documentation/networking/bonding.txt.
|
||||
|
||||
package nic
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// WithBond defines if the interface should be bonded.
|
||||
func WithBond(o bool) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.Bonded = o
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSubInterface defines which interfaces make up the bond.
|
||||
func WithSubInterface(o ...string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var found bool
|
||||
|
||||
for _, ifname := range o {
|
||||
found = false
|
||||
|
||||
for _, subif := range n.SubInterfaces {
|
||||
if ifname == subif.Name {
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
|
||||
var iface *net.Interface
|
||||
|
||||
iface, err = net.InterfaceByName(ifname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.SubInterfaces = append(n.SubInterfaces, iface)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithBondMode sets the mode the bond should operate in.
|
||||
func WithBondMode(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var mode BondMode
|
||||
|
||||
if mode, err = BondModeByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_MODE), uint8(mode))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithHashPolicy configures the transmit hash policy for the bonded interface.
|
||||
func WithHashPolicy(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var policy BondXmitHashPolicy
|
||||
|
||||
if policy, err = BondXmitHashPolicyByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_XMIT_HASH_POLICY), uint8(policy))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithLACPRate configures the bond LACP rate.
|
||||
func WithLACPRate(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var rate LACPRate
|
||||
|
||||
if rate, err = LACPRateByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_AD_LACP_RATE), uint8(rate))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithUpDelay configures the up delay for interfaces that makes up a bond.
|
||||
// The value is given in ms.
|
||||
func WithUpDelay(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_UPDELAY), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithDownDelay configures the down delay for interfaces that makes up a bond.
|
||||
// The value is given in ms.
|
||||
func WithDownDelay(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_DOWNDELAY), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithMIIMon configures the miimon interval for a bond.
|
||||
// The value is given in ms.
|
||||
func WithMIIMon(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_MIIMON), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithUseCarrier configures how miimon will determine the link status.
|
||||
func WithUseCarrier(o bool) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
// default to 1
|
||||
var carrier uint8 = 1
|
||||
|
||||
if !o {
|
||||
carrier = 0
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_USE_CARRIER), carrier)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithARPInterval specifies the ARP link monitoring frequency in milliseconds.
|
||||
func WithARPInterval(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_ARP_INTERVAL), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithARPValidate specifies whether or not ARP probes and replies should be
|
||||
// validated.
|
||||
func WithARPValidate(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var valid ARPValidate
|
||||
|
||||
if valid, err = ARPValidateByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_ARP_VALIDATE), uint32(valid))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithARPAllTargets specifies the quantity of arp_ip_targets that must be
|
||||
// reachable in order for the ARP monitor to consider a slave as being up.
|
||||
func WithARPAllTargets(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var target ARPAllTargets
|
||||
|
||||
if target, err = ARPAllTargetsByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_ARP_ALL_TARGETS), uint32(target))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrimary specifies which slave is the primary device.
|
||||
func WithPrimary(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var iface *net.Interface
|
||||
|
||||
if iface, err = net.InterfaceByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_PRIMARY_RESELECT), uint8(iface.Index))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrimaryReselect specifies the reselection policy for the primary slave.
|
||||
func WithPrimaryReselect(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var primary PrimaryReselect
|
||||
|
||||
if primary, err = PrimaryReselectByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_PRIMARY_RESELECT), uint8(primary))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithFailOverMAC specifies whether active-backup mode should set all
|
||||
// slaves to the same MAC address.
|
||||
func WithFailOverMAC(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var fo FailOverMAC
|
||||
|
||||
if fo, err = FailOverMACByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_FAIL_OVER_MAC), uint8(fo))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithResendIGMP specifies the number of IGMP membership reports to be issued
|
||||
// after a failover event.
|
||||
func WithResendIGMP(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_RESEND_IGMP), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithNumPeerNotif specifies the number of peer notifications (gratuitous ARPs and
|
||||
// unsolicited IPv6 Neighbor Advertisements) to be issued after a failover event.
|
||||
func WithNumPeerNotif(o uint8) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_NUM_PEER_NOTIF), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllSlavesActive specifies that duplicate frames (received on inactive
|
||||
// ports) should be dropped (0) or delivered (1).
|
||||
func WithAllSlavesActive(o uint8) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_ALL_SLAVES_ACTIVE), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithMinLinks specifies the minimum number of links that must be active
|
||||
// before asserting carrier.
|
||||
func WithMinLinks(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_MIN_LINKS), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithLPInterval specifies the number of seconds between instances where
|
||||
// the bonding driver sends learning packets to each slaves peer switch.
|
||||
func WithLPInterval(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_LP_INTERVAL), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithPacketsPerSlave specify the number of packets to transmit through
|
||||
// a slave before moving to the next one.
|
||||
func WithPacketsPerSlave(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_PACKETS_PER_SLAVE), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithADSelect specifies the 802.3ad aggregation selection logic to use.
|
||||
func WithADSelect(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
var sel ADSelect
|
||||
|
||||
if sel, err = ADSelectByName(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_AD_SELECT), uint8(sel))
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithADActorSysPrio in an AD system, this specifies the system priority.
|
||||
func WithADActorSysPrio(o uint16) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint16(uint16(IFLA_BOND_AD_ACTOR_SYS_PRIO), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithADUserPortKey specifies the upper 10 bits of the port key.
|
||||
func WithADUserPortKey(o uint16) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint16(uint16(IFLA_BOND_AD_USER_PORT_KEY), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithTLBDynamicLB specifies if dynamic shuffling of flows is enabled in
|
||||
// tlb mode.
|
||||
func WithTLBDynamicLB(o uint8) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint8(uint16(IFLA_BOND_TLB_DYNAMIC_LB), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithPeerNotifyDelay specifies the delay between each peer notification
|
||||
// (gratuitous ARP and unsolicited IPv6 Neighbor Advertisement) when they
|
||||
// are issued after a failover event.
|
||||
// The value is given in ms.
|
||||
func WithPeerNotifyDelay(o uint32) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Uint32(uint16(IFLA_BOND_PEER_NOTIF_DELAY), o)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// o = mac addr
|
||||
// [IFLA_BOND_AD_ACTOR_SYSTEM] = { .type = NLA_BINARY, .len = ETH_ALEN },
|
||||
func WithADActorSystem(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Not sure this is the right way to do nested attributes
|
||||
func WithARPIPTarget(o []string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.BondSettings.Nested(uint16(IFLA_BOND_ARP_IP_TARGET), netlink.NewAttributeEncoder().String(uint16(IFLA_BOND_ARP_IP_TARGET), strings.Join(",", o)))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// [IFLA_BOND_AD_INFO] = { .type = NLA_NESTED },
|
||||
// func WithInfo(o []string) Option {
|
||||
|
||||
// Not adding Active Slave since this is more of a
|
||||
// runtime adjustment versus config
|
||||
// [IFLA_BOND_ACTIVE_SLAVE] = { .type = NLA_U32 },
|
||||
*/
|
@ -1,152 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package nic
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
// createLink creates an interface.
|
||||
func (n *NetworkInterface) createLink(name string, info *rtnetlink.LinkInfo) error {
|
||||
err := n.rtConn.Link.New(&rtnetlink.LinkMessage{
|
||||
Family: unix.AF_UNSPEC,
|
||||
Type: 0,
|
||||
Attributes: &rtnetlink.LinkAttributes{
|
||||
Name: name,
|
||||
Info: info,
|
||||
},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// createLink creates an interface.
|
||||
func (n *NetworkInterface) createSubLink(name string, info *rtnetlink.LinkInfo, master *uint32) error {
|
||||
err := n.rtConn.Link.New(&rtnetlink.LinkMessage{
|
||||
Family: unix.AF_UNSPEC,
|
||||
Type: 0,
|
||||
Attributes: &rtnetlink.LinkAttributes{
|
||||
Name: name,
|
||||
Info: info,
|
||||
Type: *master,
|
||||
},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// setMTU sets the link MTU.
|
||||
func (n *NetworkInterface) setMTU(idx int, mtu uint32) error {
|
||||
msg, err := n.rtConn.Link.Get(uint32(idx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg.Attributes != nil && msg.Attributes.MTU == mtu {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = n.rtConn.Link.Set(&rtnetlink.LinkMessage{
|
||||
Family: msg.Family,
|
||||
Type: msg.Type,
|
||||
Index: uint32(idx),
|
||||
Flags: msg.Flags,
|
||||
Change: 0,
|
||||
Attributes: &rtnetlink.LinkAttributes{
|
||||
MTU: mtu,
|
||||
},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (n *NetworkInterface) configureBond(idx int, attrs *netlink.AttributeEncoder) error {
|
||||
// Request the details of the interface
|
||||
msg, err := n.rtConn.Link.Get(uint32(idx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nlAttrBytes, err := attrs.Encode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = n.rtConn.Link.Set(&rtnetlink.LinkMessage{
|
||||
Family: unix.AF_UNSPEC,
|
||||
Type: msg.Type,
|
||||
Index: msg.Index,
|
||||
Change: 0,
|
||||
Attributes: &rtnetlink.LinkAttributes{
|
||||
Info: &rtnetlink.LinkInfo{
|
||||
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/if_link.h#L612
|
||||
Kind: "bond",
|
||||
Data: nlAttrBytes,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NetworkInterface) configureWireguard(name string, config *wgtypes.Config) error {
|
||||
c, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer c.Close() //nolint:errcheck
|
||||
|
||||
return c.ConfigureDevice(name, *config)
|
||||
}
|
||||
|
||||
func (n *NetworkInterface) enslaveLink(bondIndex *uint32, links ...*net.Interface) error {
|
||||
// Set the interface operationally UP
|
||||
for _, iface := range links {
|
||||
// Request the details of the interface
|
||||
msg, err := n.rtConn.Link.Get(uint32(iface.Index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// rtnl.Down
|
||||
err = n.rtConn.Link.Set(&rtnetlink.LinkMessage{
|
||||
Family: msg.Family,
|
||||
Type: msg.Type,
|
||||
Index: uint32(iface.Index),
|
||||
Flags: 0,
|
||||
Change: unix.IFF_UP,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set link master to bond interface
|
||||
err = n.rtConn.Link.Set(&rtnetlink.LinkMessage{
|
||||
Family: msg.Family,
|
||||
Type: msg.Type,
|
||||
Index: uint32(iface.Index),
|
||||
Change: 0,
|
||||
Flags: 0,
|
||||
Attributes: &rtnetlink.LinkAttributes{
|
||||
Master: bondIndex,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,486 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package nic provides a way to describe and configure a network interface.
|
||||
package nic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/jsimonetti/rtnetlink/rtnl"
|
||||
"github.com/mdlayher/netlink"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/vip"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
// ref: https://tools.ietf.org/html/rfc791
|
||||
|
||||
// MinimumMTU is the lowest allowed MTU for an interface.
|
||||
MinimumMTU = 68
|
||||
// MaximumMTU is the highest allowed MTU for an interface.
|
||||
MaximumMTU = 65536
|
||||
)
|
||||
|
||||
// NetworkInterface provides an abstract configuration representation for a
|
||||
// network interface.
|
||||
type NetworkInterface struct {
|
||||
Name string
|
||||
Type int
|
||||
Ignore bool
|
||||
Dummy bool
|
||||
Bonded bool
|
||||
Wireguard bool
|
||||
MTU uint32
|
||||
Link *net.Interface
|
||||
SubInterfaces []*net.Interface
|
||||
AddressMethod []address.Addressing
|
||||
BondSettings *netlink.AttributeEncoder
|
||||
Vlans []*Vlan
|
||||
VirtualIP net.IP
|
||||
WireguardConfig *wgtypes.Config
|
||||
|
||||
rtConn *rtnetlink.Conn
|
||||
rtnlConn *rtnl.Conn
|
||||
|
||||
vipController vip.Controller
|
||||
}
|
||||
|
||||
// New returns a NetworkInterface with all of the given setter options applied.
|
||||
func New(setters ...Option) (*NetworkInterface, error) {
|
||||
// Default interface setup
|
||||
iface := defaultOptions()
|
||||
|
||||
// Configure interface with any specified options
|
||||
var result *multierror.Error
|
||||
for _, setter := range setters {
|
||||
result = multierror.Append(result, setter(iface))
|
||||
}
|
||||
|
||||
// TODO: May need to look at switching this around to filter by Interface.HardwareAddr
|
||||
// Ensure we have an interface name defined
|
||||
if iface.Name == "" {
|
||||
result = multierror.Append(result, errors.New("interface must have a name"))
|
||||
}
|
||||
|
||||
// If no addressing methods have been configured, default to DHCP.
|
||||
// If VLANs exist do not force DHCP on master device
|
||||
if len(iface.AddressMethod) == 0 && len(iface.Vlans) == 0 {
|
||||
iface.AddressMethod = append(iface.AddressMethod, &address.DHCP4{}) // TODO: enable DHCPv6 by default?
|
||||
}
|
||||
|
||||
// Handle netlink connection
|
||||
conn, err := rtnl.Dial(nil)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
|
||||
return nil, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
iface.rtnlConn = conn
|
||||
|
||||
// Need rtnetlink for MTU and bond settings
|
||||
nlConn, err := rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
|
||||
return nil, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
iface.rtConn = nlConn
|
||||
|
||||
return iface, result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// IsIgnored checks the network interface to see if it should be ignored and not configured.
|
||||
func (n *NetworkInterface) IsIgnored() bool {
|
||||
if n.Ignore || procfs.ProcCmdline().Get(constants.KernelParamNetworkInterfaceIgnore).Contains(n.Name) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Create creates the underlying link if it does not already exist.
|
||||
func (n *NetworkInterface) Create() error {
|
||||
var info *rtnetlink.LinkInfo
|
||||
|
||||
iface, err := net.InterfaceByName(n.Name)
|
||||
if err == nil {
|
||||
n.Link = iface
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case n.Bonded:
|
||||
info = &rtnetlink.LinkInfo{Kind: "bond"}
|
||||
case n.Dummy:
|
||||
info = &rtnetlink.LinkInfo{Kind: "dummy"}
|
||||
case n.Wireguard:
|
||||
info = &rtnetlink.LinkInfo{Kind: "wireguard"}
|
||||
default:
|
||||
return fmt.Errorf("unknown device type")
|
||||
}
|
||||
|
||||
if err = n.createLink(n.Name, info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iface, err = net.InterfaceByName(n.Name)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.Link = iface
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSub create VLAN devices that belongs to a master device.
|
||||
func (n *NetworkInterface) CreateSub(logger *log.Logger) error {
|
||||
var info *rtnetlink.LinkInfo
|
||||
|
||||
// Create all the VLAN devices
|
||||
for _, vlan := range n.Vlans {
|
||||
name := n.Name + "." + strconv.Itoa(int(vlan.ID))
|
||||
logger.Printf("setting up %s", name)
|
||||
iface, err := net.InterfaceByName(name)
|
||||
|
||||
if err == nil {
|
||||
vlan.Link = iface
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := vlan.VlanSettings.Encode()
|
||||
if err != nil {
|
||||
logger.Println("failed to encode vlan link parameters: " + err.Error())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Vlan devices needs the master link index
|
||||
masterIdx := uint32(n.Link.Index)
|
||||
info = &rtnetlink.LinkInfo{Kind: "vlan", Data: data}
|
||||
|
||||
if err = n.createSubLink(name, info, &masterIdx); err != nil {
|
||||
logger.Println("failed to create vlan link " + err.Error())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
iface, err = net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
logger.Println("failed to get vlan interface ")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
vlan.Link = iface
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure is used to set the link state and configure any necessary
|
||||
// bond settings ( ex, mode ).
|
||||
//nolint:gocyclo
|
||||
func (n *NetworkInterface) Configure(ctx context.Context) (err error) {
|
||||
if n.IsIgnored() {
|
||||
return err
|
||||
}
|
||||
|
||||
if n.Bonded {
|
||||
if err = n.configureBond(n.Link.Index, n.BondSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bondIndex := proto.Uint32(uint32(n.Link.Index))
|
||||
|
||||
// TODO: Add check if link is already part of a bond
|
||||
if err = n.enslaveLink(bondIndex, n.SubInterfaces...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if n.Wireguard {
|
||||
if err = n.configureWireguard(n.Name, n.WireguardConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = n.rtnlConn.LinkUp(n.Link); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = n.waitForLinkToBeUp(n.Link); err != nil {
|
||||
return fmt.Errorf("failed to bring up interface %q: %w", n.Link.Name, err)
|
||||
}
|
||||
|
||||
// Create all the VLAN devices
|
||||
for _, vlan := range n.Vlans {
|
||||
if err = n.rtnlConn.LinkUp(vlan.Link); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = n.waitForLinkToBeUp(vlan.Link); err != nil {
|
||||
return fmt.Errorf("failed to bring up interface %q: %w", vlan.Link.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunControllers is used to run additional controllers per interface.
|
||||
func (n *NetworkInterface) RunControllers(ctx context.Context, logger *log.Logger, eg *errgroup.Group) (err error) {
|
||||
if n.VirtualIP != nil {
|
||||
if n.vipController, err = vip.New(n.VirtualIP.String(), n.Link.Name); err != nil {
|
||||
return fmt.Errorf("failed to create the VirtualIP controller for %q on %q: %w", n.VirtualIP, n.Link.Name, err)
|
||||
}
|
||||
|
||||
if err = n.vipController.Start(ctx, logger, eg); err != nil {
|
||||
return fmt.Errorf("failed to start the VirtualIP controller for %q on %q: %w", n.VirtualIP, n.Link.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NetworkInterface) waitForLinkToBeUp(linkDev *net.Interface) error {
|
||||
// Wait for link to report up
|
||||
var link rtnetlink.LinkMessage
|
||||
|
||||
err := retry.Constant(30*time.Second, retry.WithUnits(250*time.Millisecond), retry.WithJitter(50*time.Millisecond)).Retry(func() error {
|
||||
var err error
|
||||
link, err = n.rtConn.Link.Get(uint32(linkDev.Index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if link.Flags&unix.IFF_UP != unix.IFF_UP {
|
||||
return retry.ExpectedError(fmt.Errorf("link is not up %s", n.Link.Name))
|
||||
}
|
||||
|
||||
if link.Flags&unix.IFF_RUNNING != unix.IFF_RUNNING {
|
||||
return retry.ExpectedError(fmt.Errorf("link is not ready %s", n.Link.Name))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Addressing handles the address method for a configured interface ( dhcp/static ).
|
||||
// This is inclusive of the address itself as well as any defined routes.
|
||||
func (n *NetworkInterface) Addressing(logger *log.Logger) error {
|
||||
if n.IsIgnored() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, method := range n.AddressMethod {
|
||||
if err := n.configureInterface(logger, method, n.Link); err != nil {
|
||||
// Treat as non fatal error when failing to configure an interface
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddressingSub handles the address method for a configured sub interface ( dhcp/static ).
|
||||
// This is inclusive of the address itself as well as any defined routes.
|
||||
func (n *NetworkInterface) AddressingSub(logger *log.Logger) error {
|
||||
if n.IsIgnored() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, vlan := range n.Vlans {
|
||||
for _, method := range vlan.AddressMethod {
|
||||
if err := n.configureInterface(logger, method, vlan.Link); err != nil {
|
||||
logger.Println("failed to configure address on vlan link: " + err.Error())
|
||||
// Treat as non fatal error when failing to configure an interface
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Renew is the mechanism for keeping a dhcp lease active.
|
||||
func (n *NetworkInterface) Renew(ctx context.Context, logger *log.Logger) {
|
||||
for _, method := range n.AddressMethod {
|
||||
if method.TTL() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
go n.renew(ctx, logger, method)
|
||||
}
|
||||
}
|
||||
|
||||
// renew sets up the looping to ensure we keep the addressing information
|
||||
// up to date. We attempt to do our first reconfiguration halfway through
|
||||
// address TTL. If that fails, we'll continue to attempt to retry every
|
||||
// halflife.
|
||||
func (n *NetworkInterface) renew(ctx context.Context, logger *log.Logger, method address.Addressing) {
|
||||
const minRenewDuration = 5 * time.Second // protect from renewing too often
|
||||
|
||||
renewDuration := method.TTL() / 2
|
||||
|
||||
var err error
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-time.After(renewDuration):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
if err = n.configureInterface(logger, method, n.Link); err != nil {
|
||||
logger.Printf("failure to renew address for %q: %s", n.Name, err)
|
||||
|
||||
renewDuration = (renewDuration / 2)
|
||||
} else {
|
||||
renewDuration = method.TTL() / 2
|
||||
}
|
||||
|
||||
if renewDuration < minRenewDuration {
|
||||
renewDuration = minRenewDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// configureInterface handles the actual address discovery mechanism and
|
||||
// netlink interaction to configure the interface.
|
||||
//nolint:gocyclo,cyclop
|
||||
func (n *NetworkInterface) configureInterface(logger *log.Logger, method address.Addressing, link *net.Interface) error {
|
||||
var err error
|
||||
|
||||
discoverErr := method.Discover(context.Background(), logger, link)
|
||||
|
||||
// Set link MTU in any case
|
||||
if err = n.setMTU(method.Link().Index, method.MTU()); err != nil {
|
||||
return fmt.Errorf("error setting MTU %d on %q: %w", method.MTU(), n.Name, err)
|
||||
}
|
||||
|
||||
if discoverErr != nil {
|
||||
return discoverErr
|
||||
}
|
||||
|
||||
if method.Address() != nil {
|
||||
// Check to see if we need to configure the address
|
||||
var addrs []*net.IPNet
|
||||
|
||||
addrs, err = n.rtnlConn.Addrs(method.Link(), method.Family())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addressExists := false
|
||||
|
||||
for _, addr := range addrs {
|
||||
if method.Address().String() == addr.String() {
|
||||
addressExists = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !addressExists && method.Address() != nil {
|
||||
if err = n.rtnlConn.AddrAdd(method.Link(), method.Address()); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *netlink.OpError:
|
||||
if !os.IsExist(err.Err) && err.Err != syscall.ESRCH {
|
||||
return fmt.Errorf("error adding address %s on %q: %w", method.Address(), n.Name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("failed to add address (already exists) %+v to %s: %v", method.Address(), method.Link().Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add any routes
|
||||
for _, r := range method.Routes() {
|
||||
// If gateway/router is 0.0.0.0 we'll set to nil so route scope decision will be correct
|
||||
gw := r.Gateway
|
||||
if net.IPv4zero.Equal(gw) || net.IPv6zero.Equal(gw) {
|
||||
gw = nil
|
||||
}
|
||||
|
||||
src := method.Address()
|
||||
// if destination is the ipv6 default route,and gateway is LL do not pass a src address to set the default geteway
|
||||
if net.IPv6zero.Equal(r.Destination.IP) && gw.IsLinkLocalUnicast() {
|
||||
src = nil
|
||||
}
|
||||
|
||||
attr := rtnetlink.RouteAttributes{
|
||||
Dst: r.Destination.IP,
|
||||
OutIface: uint32(method.Link().Index),
|
||||
Priority: r.Metric,
|
||||
}
|
||||
|
||||
if gw != nil {
|
||||
attr.Gateway = gw
|
||||
}
|
||||
|
||||
err = n.rtnlConn.RouteAdd(method.Link(), *r.Destination, gw, rtnl.WithRouteSrc(src), rtnl.WithRouteAttrs(attr))
|
||||
if err != nil {
|
||||
// ignore "EEXIST" errors for routes which are already present
|
||||
if opErr, ok := err.(*netlink.OpError); !ok || !os.IsExist(opErr.Err) {
|
||||
return fmt.Errorf("error adding route %s %s on %q: %s", *r.Destination, gw, n.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset removes addressing configuration from a given link.
|
||||
func (n *NetworkInterface) Reset() {
|
||||
var (
|
||||
err error
|
||||
link *net.Interface
|
||||
nets []*net.IPNet
|
||||
)
|
||||
|
||||
link, err = net.InterfaceByName(n.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if nets, err = n.rtnlConn.Addrs(link, 0); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ipnet := range nets {
|
||||
if err = n.rtnlConn.AddrDel(link, ipnet); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
n.rtnlConn.LinkDown(link)
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package nic_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
)
|
||||
|
||||
type NicSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestNicSuite(t *testing.T) {
|
||||
suite.Run(t, new(NicSuite))
|
||||
}
|
||||
|
||||
func (suite *NicSuite) TestIgnoreNic() {
|
||||
mynic, err := nic.New(nic.WithName("yolo"), nic.WithIgnore())
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().True(mynic.IsIgnored())
|
||||
}
|
||||
|
||||
func (suite *NicSuite) TestNoName() {
|
||||
_, err := nic.New()
|
||||
suite.Require().Error(err)
|
||||
}
|
||||
|
||||
func (suite *NicSuite) TestBond() {
|
||||
testSettings := [][]nic.Option{
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
},
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
nic.WithBondMode("balance-xor"),
|
||||
},
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
nic.WithBondMode("802.3ad"),
|
||||
nic.WithHashPolicy("layer3+4"),
|
||||
},
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
nic.WithBondMode("balance-tlb"),
|
||||
nic.WithHashPolicy("encap3+4"),
|
||||
nic.WithLACPRate("fast"),
|
||||
},
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
nic.WithBondMode("balance-alb"),
|
||||
nic.WithHashPolicy("encap2+3"),
|
||||
nic.WithLACPRate("slow"),
|
||||
nic.WithUpDelay(200),
|
||||
},
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
nic.WithBondMode("broadcast"),
|
||||
nic.WithHashPolicy("layer2+3"),
|
||||
nic.WithLACPRate("fast"),
|
||||
nic.WithUpDelay(300),
|
||||
nic.WithDownDelay(400),
|
||||
nic.WithMIIMon(500),
|
||||
},
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
nic.WithBondMode("balance-rr"),
|
||||
nic.WithHashPolicy("layer2"),
|
||||
nic.WithLACPRate("slow"),
|
||||
nic.WithUpDelay(300),
|
||||
nic.WithDownDelay(400),
|
||||
nic.WithMIIMon(500),
|
||||
nic.WithSubInterface("lo", "lo"),
|
||||
},
|
||||
{
|
||||
nic.WithName("yolobond"),
|
||||
nic.WithBond(true),
|
||||
nic.WithBondMode("active-backup"),
|
||||
nic.WithHashPolicy("layer2"),
|
||||
nic.WithLACPRate("slow"),
|
||||
nic.WithUpDelay(300),
|
||||
nic.WithDownDelay(400),
|
||||
nic.WithMIIMon(500),
|
||||
nic.WithSubInterface("lo", "lo"),
|
||||
nic.WithAddressing(&address.Static{}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testSettings {
|
||||
mynic, err := nic.New(test...)
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().True(mynic.Bonded)
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *NicSuite) TestVlan() {
|
||||
testSettings := [][]nic.Option{
|
||||
{
|
||||
nic.WithName("eth0"),
|
||||
nic.WithVlan(100),
|
||||
},
|
||||
{
|
||||
nic.WithName("eth0"),
|
||||
nic.WithVlan(100),
|
||||
nic.WithVlanCIDR(100, "172.21.10.101/28", []config.Route{}),
|
||||
},
|
||||
}
|
||||
for _, test := range testSettings {
|
||||
mynic, err := nic.New(test...)
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().True(len(mynic.Vlans) > 0)
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package nic
|
||||
|
||||
import (
|
||||
"github.com/mdlayher/netlink"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
||||
)
|
||||
|
||||
// Option is the functional option func.
|
||||
type Option func(*NetworkInterface) error
|
||||
|
||||
// defaultOptions defines our default network interface configuration.
|
||||
func defaultOptions() *NetworkInterface {
|
||||
return &NetworkInterface{
|
||||
Bonded: false,
|
||||
MTU: 1500,
|
||||
AddressMethod: []address.Addressing{},
|
||||
BondSettings: netlink.NewAttributeEncoder(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithDummy indicates that the interface should be a virtual, dummy interface.
|
||||
func WithDummy() Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.Dummy = true
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithIgnore indicates that the interface should not be processed by talos.
|
||||
func WithIgnore() Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.Ignore = true
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the name of the interface to the given name.
|
||||
func WithName(o string) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.Name = o
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithAddressing defines how the addressing for a given interface
|
||||
// should be configured.
|
||||
func WithAddressing(a address.Addressing) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.AddressMethod = append(n.AddressMethod, a)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoAddressing defines how the addressing for a given interface
|
||||
// should be configured.
|
||||
func WithNoAddressing() Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.AddressMethod = []address.Addressing{}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
@ -1,322 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//nolint:golint,stylecheck,revive
|
||||
package nic
|
||||
|
||||
import "fmt"
|
||||
|
||||
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/if_link.h#L608
|
||||
type BondSetting uint16
|
||||
|
||||
const (
|
||||
IFLA_BOND_UNSPEC BondSetting = iota
|
||||
IFLA_BOND_MODE
|
||||
IFLA_BOND_ACTIVE_SLAVE
|
||||
IFLA_BOND_MIIMON
|
||||
IFLA_BOND_UPDELAY
|
||||
IFLA_BOND_DOWNDELAY
|
||||
IFLA_BOND_USE_CARRIER
|
||||
IFLA_BOND_ARP_INTERVAL
|
||||
IFLA_BOND_ARP_IP_TARGET
|
||||
IFLA_BOND_ARP_VALIDATE
|
||||
IFLA_BOND_ARP_ALL_TARGETS
|
||||
IFLA_BOND_PRIMARY
|
||||
IFLA_BOND_PRIMARY_RESELECT
|
||||
IFLA_BOND_FAIL_OVER_MAC
|
||||
IFLA_BOND_XMIT_HASH_POLICY
|
||||
IFLA_BOND_RESEND_IGMP
|
||||
IFLA_BOND_NUM_PEER_NOTIF
|
||||
IFLA_BOND_ALL_SLAVES_ACTIVE
|
||||
IFLA_BOND_MIN_LINKS
|
||||
IFLA_BOND_LP_INTERVAL
|
||||
IFLA_BOND_PACKETS_PER_SLAVE
|
||||
IFLA_BOND_AD_LACP_RATE
|
||||
IFLA_BOND_AD_SELECT
|
||||
IFLA_BOND_AD_INFO
|
||||
IFLA_BOND_AD_ACTOR_SYS_PRIO
|
||||
IFLA_BOND_AD_USER_PORT_KEY
|
||||
IFLA_BOND_AD_ACTOR_SYSTEM
|
||||
IFLA_BOND_TLB_DYNAMIC_LB
|
||||
IFLA_BOND_PEER_NOTIF_DELAY
|
||||
)
|
||||
|
||||
func (b BondSetting) String() string {
|
||||
return [...]string{
|
||||
"unspec",
|
||||
"mode",
|
||||
"active slave",
|
||||
"miimon",
|
||||
"updelay",
|
||||
"downdelay",
|
||||
"use carrier",
|
||||
"arp interval",
|
||||
"arp ip target",
|
||||
"arp validate",
|
||||
"arp all targets",
|
||||
"primary",
|
||||
"primary reselect",
|
||||
"fail over mac",
|
||||
"xmit hash policy",
|
||||
"resend igmp",
|
||||
"num peer notif",
|
||||
"all slaves active",
|
||||
"min links",
|
||||
"lp interval",
|
||||
"packets per slave",
|
||||
"ad lacp rate",
|
||||
"ad select",
|
||||
"ad innfo",
|
||||
"ad actor sys prio",
|
||||
"ad user port key",
|
||||
"ad actor system",
|
||||
"tlb dynamic lb",
|
||||
"peer notif delay",
|
||||
}[int(b)]
|
||||
}
|
||||
|
||||
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/if_bonding.h
|
||||
type BondMode uint8
|
||||
|
||||
const (
|
||||
BOND_MODE_ROUNDROBIN BondMode = iota
|
||||
BOND_MODE_ACTIVEBACKUP
|
||||
BOND_MODE_XOR
|
||||
BOND_MODE_BROADCAST
|
||||
BOND_MODE_8023AD
|
||||
BOND_MODE_TLB
|
||||
BOND_MODE_ALB
|
||||
)
|
||||
|
||||
func (b BondMode) String() string {
|
||||
return [...]string{"balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad", "balance-tlb", "balance-alb"}[int(b)]
|
||||
}
|
||||
|
||||
func BondModeByName(mode string) (bm BondMode, err error) {
|
||||
switch mode {
|
||||
case "balance-rr":
|
||||
bm = BOND_MODE_ROUNDROBIN
|
||||
case "active-backup":
|
||||
bm = BOND_MODE_ACTIVEBACKUP
|
||||
case "balance-xor":
|
||||
bm = BOND_MODE_XOR
|
||||
case "broadcast":
|
||||
bm = BOND_MODE_BROADCAST
|
||||
case "802.3ad":
|
||||
bm = BOND_MODE_8023AD
|
||||
case "balance-tlb":
|
||||
bm = BOND_MODE_TLB
|
||||
case "balance-alb":
|
||||
bm = BOND_MODE_ALB
|
||||
default:
|
||||
err = fmt.Errorf("invalid bond type %s", mode)
|
||||
}
|
||||
|
||||
return bm, err
|
||||
}
|
||||
|
||||
type BondXmitHashPolicy uint8
|
||||
|
||||
const (
|
||||
BOND_XMIT_POLICY_LAYER2 BondXmitHashPolicy = iota
|
||||
BOND_XMIT_POLICY_LAYER34
|
||||
BOND_XMIT_POLICY_LAYER23
|
||||
BOND_XMIT_POLICY_ENCAP23
|
||||
BOND_XMIT_POLICY_ENCAP34
|
||||
)
|
||||
|
||||
func (b BondXmitHashPolicy) String() string {
|
||||
return [...]string{"layer2", "layer3+4", "layer2+3", "encap2+3", "encap3+4"}[int(b)]
|
||||
}
|
||||
|
||||
func BondXmitHashPolicyByName(policy string) (xmit BondXmitHashPolicy, err error) {
|
||||
switch policy {
|
||||
case "layer2":
|
||||
xmit = BOND_XMIT_POLICY_LAYER2
|
||||
case "layer3+4":
|
||||
xmit = BOND_XMIT_POLICY_LAYER34
|
||||
case "layer2+3":
|
||||
xmit = BOND_XMIT_POLICY_LAYER23
|
||||
case "encap2+3":
|
||||
xmit = BOND_XMIT_POLICY_ENCAP23
|
||||
case "encap3+4":
|
||||
xmit = BOND_XMIT_POLICY_ENCAP34
|
||||
default:
|
||||
err = fmt.Errorf("invalid xmit hash policy %v", xmit)
|
||||
}
|
||||
|
||||
return xmit, err
|
||||
}
|
||||
|
||||
type LACPRate uint8
|
||||
|
||||
const (
|
||||
LACP_RATE_SLOW LACPRate = iota
|
||||
LACP_RATE_FAST
|
||||
)
|
||||
|
||||
func (l LACPRate) String() string {
|
||||
return [...]string{"slow", "fast"}[l]
|
||||
}
|
||||
|
||||
func LACPRateByName(mode string) (rate LACPRate, err error) {
|
||||
switch mode {
|
||||
case "slow":
|
||||
rate = LACP_RATE_SLOW
|
||||
case "fast":
|
||||
rate = LACP_RATE_FAST
|
||||
default:
|
||||
err = fmt.Errorf("invalid lacp rate %v", mode)
|
||||
}
|
||||
|
||||
return rate, err
|
||||
}
|
||||
|
||||
type ADSelect uint8
|
||||
|
||||
const (
|
||||
AD_SELECT_STABLE ADSelect = iota
|
||||
AD_SELECT_BANDWIDTH
|
||||
AD_SELECT_COUNT
|
||||
)
|
||||
|
||||
func (a ADSelect) String() string {
|
||||
return [...]string{"stable", "bandwidth", "count"}[a]
|
||||
}
|
||||
|
||||
func ADSelectByName(sel string) (adsel ADSelect, err error) {
|
||||
switch sel {
|
||||
case "stable":
|
||||
adsel = AD_SELECT_STABLE
|
||||
case "bandwidth":
|
||||
adsel = AD_SELECT_BANDWIDTH
|
||||
case "count":
|
||||
adsel = AD_SELECT_COUNT
|
||||
default:
|
||||
err = fmt.Errorf("invalid ad_select mode %v", sel)
|
||||
}
|
||||
|
||||
return adsel, err
|
||||
}
|
||||
|
||||
type ARPValidate uint32
|
||||
|
||||
const (
|
||||
ARP_VALIDATE_NONE ARPValidate = iota
|
||||
ARP_VALIDATE_ACTIVE
|
||||
ARP_VALIDATE_BACKUP
|
||||
ARP_VALIDATE_ALL
|
||||
)
|
||||
|
||||
func (a ARPValidate) String() string {
|
||||
return [...]string{"none", "active", "backup", "all"}[a]
|
||||
}
|
||||
|
||||
func ARPValidateByName(a string) (arpv ARPValidate, err error) {
|
||||
switch a {
|
||||
case "none":
|
||||
arpv = ARP_VALIDATE_NONE
|
||||
case "active":
|
||||
arpv = ARP_VALIDATE_ACTIVE
|
||||
case "backup":
|
||||
arpv = ARP_VALIDATE_BACKUP
|
||||
case "all":
|
||||
arpv = ARP_VALIDATE_ALL
|
||||
default:
|
||||
err = fmt.Errorf("invalid arp_validate mode %v", a)
|
||||
}
|
||||
|
||||
return arpv, err
|
||||
}
|
||||
|
||||
type ARPAllTargets uint32
|
||||
|
||||
const (
|
||||
ARP_ALL_TARGETS_ANY ARPAllTargets = iota
|
||||
ARP_ALL_TARGETS_ALL
|
||||
)
|
||||
|
||||
func (a ARPAllTargets) String() string {
|
||||
return [...]string{"any", "all"}[a]
|
||||
}
|
||||
|
||||
func ARPAllTargetsByName(a string) (arpa ARPAllTargets, err error) {
|
||||
switch a {
|
||||
case "any":
|
||||
arpa = ARP_ALL_TARGETS_ANY
|
||||
case "all":
|
||||
arpa = ARP_ALL_TARGETS_ALL
|
||||
default:
|
||||
err = fmt.Errorf("invalid arp_all_targets mode %v", a)
|
||||
}
|
||||
|
||||
return arpa, err
|
||||
}
|
||||
|
||||
type PrimaryReselect uint8
|
||||
|
||||
const (
|
||||
PRIMARY_RESELECT_ALWAYS PrimaryReselect = iota
|
||||
PRIMARY_RESELECT_BETTER
|
||||
PRIMARY_RESELECT_FAILURE
|
||||
)
|
||||
|
||||
func (p PrimaryReselect) String() string {
|
||||
return [...]string{"always", "better", "failure"}[p]
|
||||
}
|
||||
|
||||
func PrimaryReselectByName(p string) (pr PrimaryReselect, err error) {
|
||||
switch p {
|
||||
case "always":
|
||||
pr = PRIMARY_RESELECT_ALWAYS
|
||||
case "better":
|
||||
pr = PRIMARY_RESELECT_BETTER
|
||||
case "failure":
|
||||
pr = PRIMARY_RESELECT_FAILURE
|
||||
default:
|
||||
err = fmt.Errorf("invalid primary_reselect mode %v", p)
|
||||
}
|
||||
|
||||
return pr, err
|
||||
}
|
||||
|
||||
type FailOverMAC uint8
|
||||
|
||||
const (
|
||||
FAIL_OVER_MAC_NONE FailOverMAC = iota
|
||||
FAIL_OVER_MAC_ACTIVE
|
||||
FAIL_OVER_MAC_FOLLOW
|
||||
)
|
||||
|
||||
func FailOverMACByName(f string) (fo FailOverMAC, err error) {
|
||||
switch f {
|
||||
case "none":
|
||||
fo = FAIL_OVER_MAC_NONE
|
||||
case "active":
|
||||
fo = FAIL_OVER_MAC_ACTIVE
|
||||
case "follow":
|
||||
fo = FAIL_OVER_MAC_FOLLOW
|
||||
default:
|
||||
err = fmt.Errorf("invalid fail_over_mac value %v", f)
|
||||
}
|
||||
|
||||
return fo, err
|
||||
}
|
||||
|
||||
const (
|
||||
IFLA_VLAN_UNSPEC = iota
|
||||
IFLA_VLAN_ID
|
||||
IFLA_VLAN_FLAGS
|
||||
IFLA_VLAN_EGRESS_QOS
|
||||
IFLA_VLAN_INGRESS_QOS
|
||||
IFLA_VLAN_PROTOCOL
|
||||
IFLA_VLAN_MAX = IFLA_VLAN_PROTOCOL
|
||||
)
|
||||
|
||||
// VlanProtocol possible values.
|
||||
const (
|
||||
VLAN_PROTOCOL_UNKNOWN = 0
|
||||
VLAN_PROTOCOL_8021Q = 0x8100
|
||||
VLAN_PROTOCOL_8021AD = 0x88A8
|
||||
)
|
@ -1,29 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Additional information can be found
|
||||
// https://www.kernel.org/doc/Documentation/networking/bonding.txt.
|
||||
|
||||
package nic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
)
|
||||
|
||||
// WithVIPConfig adapts a talosconfig VIP configuration to a networkd interface configuration option.
|
||||
func WithVIPConfig(cfg config.VIPConfig) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
sharedIP := net.ParseIP(cfg.IP())
|
||||
if sharedIP == nil {
|
||||
return fmt.Errorf("failed to parse shared IP %q as an IP address", cfg.IP())
|
||||
}
|
||||
|
||||
n.VirtualIP = sharedIP
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package nic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
)
|
||||
|
||||
// Vlan contins interface related parameters to a VLAN device.
|
||||
type Vlan struct {
|
||||
Parent string
|
||||
ID uint16
|
||||
Link *net.Interface
|
||||
VlanSettings *netlink.AttributeEncoder
|
||||
AddressMethod []address.Addressing
|
||||
}
|
||||
|
||||
// WithVlan defines the VLAN id to use.
|
||||
func WithVlan(id uint16) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
for _, vlan := range n.Vlans {
|
||||
if vlan.ID == id {
|
||||
return fmt.Errorf("duplicate VLAN id %v given", vlan)
|
||||
}
|
||||
}
|
||||
|
||||
vlan := &Vlan{
|
||||
ID: id,
|
||||
VlanSettings: netlink.NewAttributeEncoder(),
|
||||
}
|
||||
|
||||
vlan.VlanSettings.Uint16(uint16(IFLA_VLAN_ID), vlan.ID)
|
||||
n.Vlans = append(n.Vlans, vlan)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithVlanDhcp sets a VLAN device with DHCP.
|
||||
func WithVlanDhcp(id uint16) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
for _, vlan := range n.Vlans {
|
||||
if vlan.ID == id {
|
||||
vlan.AddressMethod = append(vlan.AddressMethod, &address.DHCP4{}) // TODO: should we enable DHCP6 by default?
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("VLAN id not found for DHCP. Vlan ID %v given", id)
|
||||
}
|
||||
}
|
||||
|
||||
// WithVlanCIDR defines if the interface have static CIDRs added.
|
||||
func WithVlanCIDR(id uint16, cidr string, routeList []config.Route) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
for _, vlan := range n.Vlans {
|
||||
if vlan.ID == id {
|
||||
vlan.AddressMethod = append(vlan.AddressMethod, &address.Static{CIDR: cidr, RouteList: routeList})
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("VLAN id not found for CIDR setting %v given", id)
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Additional information can be found
|
||||
// https://www.kernel.org/doc/Documentation/networking/bonding.txt.
|
||||
|
||||
package nic
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
)
|
||||
|
||||
// WithWireguardConfig defines if the interface should be a Wireguard interface and supplies Wireguard configs.
|
||||
//nolint:gocyclo
|
||||
func WithWireguardConfig(cfg config.WireguardConfig) Option {
|
||||
return func(n *NetworkInterface) (err error) {
|
||||
n.Wireguard = true
|
||||
|
||||
privateKey, err := wgtypes.ParseKey(cfg.PrivateKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := &wgtypes.Config{
|
||||
PrivateKey: &privateKey,
|
||||
ReplacePeers: true,
|
||||
}
|
||||
|
||||
firewallMark := cfg.FirewallMark()
|
||||
if firewallMark > 0 {
|
||||
config.FirewallMark = &firewallMark
|
||||
}
|
||||
|
||||
listenPort := cfg.ListenPort()
|
||||
|
||||
if listenPort > 0 {
|
||||
config.ListenPort = &listenPort
|
||||
}
|
||||
|
||||
config.Peers = make([]wgtypes.PeerConfig, len(cfg.Peers()))
|
||||
|
||||
for i, peer := range cfg.Peers() {
|
||||
publicKey, err := wgtypes.ParseKey(peer.PublicKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerCfg := wgtypes.PeerConfig{
|
||||
PublicKey: publicKey,
|
||||
AllowedIPs: make([]net.IPNet, len(peer.AllowedIPs())),
|
||||
}
|
||||
|
||||
if peer.Endpoint() != "" {
|
||||
peerCfg.Endpoint, err = net.ResolveUDPAddr("", peer.Endpoint())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
peerKeepaliveInterval := peer.PersistentKeepaliveInterval()
|
||||
|
||||
if peerKeepaliveInterval > 0 {
|
||||
peerCfg.PersistentKeepaliveInterval = &peerKeepaliveInterval
|
||||
}
|
||||
|
||||
for j, ip := range peer.AllowedIPs() {
|
||||
_, ip, err := net.ParseCIDR(ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerCfg.AllowedIPs[j] = *ip
|
||||
}
|
||||
|
||||
config.Peers[i] = peerCfg
|
||||
}
|
||||
|
||||
n.WireguardConfig = config
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package reg provides the gRPC network service implementation.
|
||||
package reg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/networkd"
|
||||
healthapi "github.com/talos-systems/talos/pkg/machinery/api/health"
|
||||
)
|
||||
|
||||
// Registrator is the concrete type that implements the factory.Registrator and
|
||||
// healthapi.HealthServer and networkapi.NetworkServiceServer interfaces.
|
||||
type Registrator struct {
|
||||
healthapi.UnimplementedHealthServer
|
||||
|
||||
Networkd *networkd.Networkd
|
||||
}
|
||||
|
||||
// NewRegistrator builds new Registrator instance.
|
||||
func NewRegistrator(n *networkd.Networkd) (*Registrator, error) {
|
||||
return &Registrator{
|
||||
Networkd: n,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Register implements the factory.Registrator interface.
|
||||
func (r *Registrator) Register(s *grpc.Server) {
|
||||
healthapi.RegisterHealthServer(s, r)
|
||||
}
|
||||
|
||||
// Check implements the Health api and provides visibilty into the state of networkd.
|
||||
func (r *Registrator) Check(ctx context.Context, in *empty.Empty) (reply *healthapi.HealthCheckResponse, err error) {
|
||||
reply = &healthapi.HealthCheckResponse{
|
||||
Messages: []*healthapi.HealthCheck{
|
||||
{
|
||||
Status: healthapi.HealthCheck_SERVING,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
// Watch implements the Health api and provides visibilty into the state of networkd.
|
||||
// Ready signifies the daemon (api) is healthy and ready to serve requests.
|
||||
func (r *Registrator) Watch(in *healthapi.HealthWatchRequest, srv healthapi.Health_WatchServer) (err error) {
|
||||
if in == nil {
|
||||
return errors.New("an input interval is required")
|
||||
}
|
||||
|
||||
var (
|
||||
resp *healthapi.HealthCheckResponse
|
||||
ticker = time.NewTicker(time.Duration(in.IntervalSeconds) * time.Second)
|
||||
)
|
||||
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-srv.Context().Done():
|
||||
return srv.Context().Err()
|
||||
case <-ticker.C:
|
||||
resp, err = r.Check(srv.Context(), &empty.Empty{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = srv.Send(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ready implements the Health api and provides visibility to the state of networkd.
|
||||
// Ready signifies the initial network configuration ( interfaces, routes, hostname, resolv.conf )
|
||||
// settings have been applied.
|
||||
// Not Ready signifies that the initial network configuration still needs to happen.
|
||||
func (r *Registrator) Ready(ctx context.Context, in *empty.Empty) (reply *healthapi.ReadyCheckResponse, err error) {
|
||||
rdy := &healthapi.ReadyCheck{Status: healthapi.ReadyCheck_NOT_READY}
|
||||
|
||||
if r.Networkd.Ready() {
|
||||
rdy.Status = healthapi.ReadyCheck_READY
|
||||
}
|
||||
|
||||
reply = &healthapi.ReadyCheckResponse{
|
||||
Messages: []*healthapi.ReadyCheck{
|
||||
rdy,
|
||||
},
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//nolint:testpackage
|
||||
package reg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/networkd/pkg/networkd"
|
||||
"github.com/talos-systems/talos/pkg/grpc/dialer"
|
||||
"github.com/talos-systems/talos/pkg/grpc/factory"
|
||||
healthapi "github.com/talos-systems/talos/pkg/machinery/api/health"
|
||||
)
|
||||
|
||||
type NetworkdSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestNetworkdSuite(t *testing.T) {
|
||||
suite.Run(t, new(NetworkdSuite))
|
||||
}
|
||||
|
||||
func (suite *NetworkdSuite) fakeNetworkdRPC() (*networkd.Networkd, *grpc.Server, net.Listener) {
|
||||
// Create networkd instance
|
||||
n, err := networkd.New(log.New(os.Stderr, "", log.LstdFlags), nil)
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
// Create gRPC server
|
||||
api, err := NewRegistrator(n)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
server := factory.NewServer(api)
|
||||
tmpfile, err := ioutil.TempFile("", "networkd")
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
listener, err := factory.NewListener(
|
||||
factory.Network("unix"),
|
||||
factory.SocketPath(tmpfile.Name()),
|
||||
)
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
return n, server, listener
|
||||
}
|
||||
|
||||
func (suite *NetworkdSuite) TestHealthAPI() {
|
||||
nwd, server, listener := suite.fakeNetworkdRPC()
|
||||
|
||||
//nolint:errcheck
|
||||
defer os.Remove(listener.Addr().String())
|
||||
|
||||
defer server.Stop()
|
||||
|
||||
//nolint:errcheck
|
||||
go server.Serve(listener)
|
||||
|
||||
conn, err := grpc.Dial(
|
||||
fmt.Sprintf("%s://%s", "unix", listener.Addr().String()),
|
||||
grpc.WithInsecure(),
|
||||
grpc.WithContextDialer(dialer.DialUnix()),
|
||||
)
|
||||
suite.Assert().NoError(err)
|
||||
|
||||
// Verify base state
|
||||
nClient := healthapi.NewHealthClient(conn)
|
||||
hcResp, err := nClient.Check(context.Background(), &empty.Empty{})
|
||||
suite.Assert().NoError(err)
|
||||
// Can only check against unknown since its not guaranteed that
|
||||
// the host the tests will run on will have an arp table populated.
|
||||
suite.Assert().NotEqual(healthapi.HealthCheck_UNKNOWN, hcResp.Messages[0].Status)
|
||||
|
||||
rResp, err := nClient.Ready(context.Background(), &empty.Empty{})
|
||||
suite.Assert().NoError(err)
|
||||
suite.Assert().Equal(healthapi.ReadyCheck_NOT_READY, rResp.Messages[0].Status)
|
||||
|
||||
// Trigger ready condition
|
||||
nwd.SetReady()
|
||||
suite.Assert().NoError(err)
|
||||
rResp, err = nClient.Ready(context.Background(), &empty.Empty{})
|
||||
suite.Assert().NoError(err)
|
||||
suite.Assert().Equal(healthapi.ReadyCheck_READY, rResp.Messages[0].Status)
|
||||
|
||||
// Test watch
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
stream, err := nClient.Watch(ctx, &healthapi.HealthWatchRequest{IntervalSeconds: 1})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
hcResp, err = stream.Recv()
|
||||
suite.Assert().NoError(err)
|
||||
suite.Assert().NotEqual(healthapi.HealthCheck_UNKNOWN, hcResp.Messages[0].Status)
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
_, err = stream.Recv()
|
||||
suite.Assert().Error(err)
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package server implements network API gRPC server.
|
||||
package server
|
||||
|
||||
import (
|
||||
|
@ -1,197 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package vip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/plunder-app/kube-vip/pkg/vip"
|
||||
"go.etcd.io/etcd/client/v3/concurrency"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/talos-systems/talos/internal/pkg/etcd"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
const campaignRetryInterval = time.Second
|
||||
|
||||
// A Controller provides a control interface for Virtual IP addressing.
|
||||
type Controller interface {
|
||||
// Start activates the Virtual IP address controller.
|
||||
Start(ctx context.Context, logger *log.Logger, eg *errgroup.Group) error
|
||||
}
|
||||
|
||||
type vipController struct {
|
||||
ip net.IP
|
||||
iface *net.Interface
|
||||
}
|
||||
|
||||
// New creates a new Virtual IP controller.
|
||||
func New(ip, iface string) (Controller, error) {
|
||||
ipaddr := net.ParseIP(ip)
|
||||
if ipaddr == nil {
|
||||
return nil, fmt.Errorf("failed to parse ip %q as an IP address", ip)
|
||||
}
|
||||
|
||||
netIf, err := net.InterfaceByName(iface)
|
||||
if err != nil || netIf == nil {
|
||||
return nil, fmt.Errorf("failed to find interface %s by name: %w", iface, err)
|
||||
}
|
||||
|
||||
return &vipController{
|
||||
ip: ipaddr,
|
||||
iface: netIf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start implements the Controller interface.
|
||||
func (c *vipController) Start(ctx context.Context, logger *log.Logger, eg *errgroup.Group) error {
|
||||
netController, err := vip.NewConfig(c.ip.String(), c.iface.Name, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
c.maintain(ctx, logger, netController)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *vipController) etcdElectionKey() string {
|
||||
return fmt.Sprintf("%s:vip:election:%s", constants.EtcdRootTalosKey, c.ip.String())
|
||||
}
|
||||
|
||||
func (c *vipController) maintain(ctx context.Context, logger *log.Logger, netController vip.Network) {
|
||||
for ctx.Err() == nil {
|
||||
if err := c.campaign(ctx, logger, netController); err != nil {
|
||||
logger.Printf("campaign failure: %s", err)
|
||||
|
||||
time.Sleep(campaignRetryInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo,cyclop
|
||||
func (c *vipController) campaign(ctx context.Context, logger *log.Logger, netController vip.Network) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return fmt.Errorf("refusing to join election without a hostname")
|
||||
}
|
||||
|
||||
ec, err := etcd.NewLocalClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create local etcd client: %w", err)
|
||||
}
|
||||
|
||||
defer ec.Close() //nolint:errcheck
|
||||
|
||||
sess, err := concurrency.NewSession(ec.Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create concurrency session: %w", err)
|
||||
}
|
||||
defer sess.Close() //nolint:errcheck
|
||||
|
||||
election := concurrency.NewElection(sess, c.etcdElectionKey())
|
||||
|
||||
node, err := election.Leader(ctx)
|
||||
if err != nil {
|
||||
if err != concurrency.ErrElectionNoLeader {
|
||||
return fmt.Errorf("failed getting current leader: %w", err)
|
||||
}
|
||||
} else if string(node.Kvs[0].Value) == hostname {
|
||||
logger.Printf("vip: resigning from previous election")
|
||||
|
||||
// we are still leader from the previous election, attempt to resign to force new election
|
||||
resumedElection := concurrency.ResumeElection(sess, c.etcdElectionKey(), string(node.Kvs[0].Key), node.Kvs[0].CreateRevision)
|
||||
|
||||
if err = resumedElection.Resign(ctx); err != nil {
|
||||
return fmt.Errorf("failed resigning from previous elections: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
campaignErrCh := make(chan error)
|
||||
|
||||
go func() {
|
||||
campaignErrCh <- election.Campaign(ctx, hostname)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err = <-campaignErrCh:
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to conduct campaign: %w", err)
|
||||
}
|
||||
case <-sess.Done():
|
||||
logger.Printf("vip: session closed")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// use a new context to resign, as `ctx` might be canceled
|
||||
resignCtx, resignCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer resignCancel()
|
||||
|
||||
election.Resign(resignCtx) //nolint:errcheck
|
||||
}()
|
||||
|
||||
if err = netController.AddIP(); err != nil {
|
||||
return fmt.Errorf("failed to add VIP %q to local interface %q: %w", c.ip.String(), c.iface.Name, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
logger.Printf("vip: removing shared IP %q on interface %q", c.ip.String(), c.iface.Name)
|
||||
|
||||
if err = netController.DeleteIP(); err != nil {
|
||||
logger.Printf("vip: error removing shared IP: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// ARP is only supported for IPv4
|
||||
if c.ip.To4() != nil {
|
||||
// Send gratuitous ARP to announce the change
|
||||
if err = vip.ARPSendGratuitous(c.ip.String(), c.iface.Name); err != nil {
|
||||
return fmt.Errorf("failed to send gratuitous ARP after winning election: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Printf("vip: enabled shared IP %q on interface %q", c.ip.String(), c.iface.Name)
|
||||
|
||||
observe := election.Observe(ctx)
|
||||
|
||||
observeLoop:
|
||||
for {
|
||||
select {
|
||||
case <-sess.Done():
|
||||
logger.Printf("vip: session closed")
|
||||
|
||||
break observeLoop
|
||||
case <-ctx.Done():
|
||||
break observeLoop
|
||||
case resp, ok := <-observe:
|
||||
if !ok {
|
||||
break observeLoop
|
||||
}
|
||||
|
||||
if string(resp.Kvs[0].Value) != hostname {
|
||||
logger.Printf("vip: detected new leader %q", string(resp.Kvs[0].Value))
|
||||
|
||||
break observeLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -91,6 +91,11 @@ func (r *StaticPodStatus) ResourceDefinition() meta.ResourceDefinitionSpec {
|
||||
}
|
||||
}
|
||||
|
||||
// Status gets pod status.
|
||||
func (r *StaticPodStatus) Status() *v1.PodStatus {
|
||||
return r.spec.PodStatus
|
||||
}
|
||||
|
||||
// SetStatus sets pod status.
|
||||
func (r *StaticPodStatus) SetStatus(status *v1.PodStatus) {
|
||||
r.spec.PodStatus = status
|
||||
|
@ -35,7 +35,7 @@ func RouteID(destination netaddr.IPPrefix, gateway netaddr.IP) string {
|
||||
dst, _ := destination.MarshalText() //nolint:errcheck
|
||||
gw, _ := gateway.MarshalText() //nolint:errcheck
|
||||
|
||||
return fmt.Sprintf("%s/%s", string(dst), string(gw))
|
||||
return fmt.Sprintf("%s/%s", string(gw), string(dst))
|
||||
}
|
||||
|
||||
// OperatorID builds ID (primary key) for the operators.
|
||||
|
@ -27,6 +27,7 @@ type RouteSpec struct {
|
||||
type RouteSpecSpec struct {
|
||||
Family nethelpers.Family `yaml:"family"`
|
||||
Destination netaddr.IPPrefix `yaml:"dst"`
|
||||
Source netaddr.IPPrefix `yaml:"src"`
|
||||
Gateway netaddr.IP `yaml:"gateway"`
|
||||
OutLinkName string `yaml:"outLinkName,omitempty"`
|
||||
Table nethelpers.RoutingTable `yaml:"table"`
|
||||
@ -38,6 +39,40 @@ type RouteSpecSpec struct {
|
||||
ConfigLayer ConfigLayer `yaml:"layer"`
|
||||
}
|
||||
|
||||
var (
|
||||
zero16 = netaddr.MustParseIP("::")
|
||||
zero4 = netaddr.MustParseIP("0.0.0.0")
|
||||
)
|
||||
|
||||
// Normalize converts 0.0.0.0 to zero value.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (route *RouteSpecSpec) Normalize() {
|
||||
if route.Destination.Bits == 0 && (route.Destination.IP.Compare(zero4) == 0 || route.Destination.IP.Compare(zero16) == 0) {
|
||||
// clear destination to be zero value to support "0.0.0.0/0" routes
|
||||
route.Destination = netaddr.IPPrefix{}
|
||||
}
|
||||
|
||||
if route.Gateway.Compare(zero4) == 0 || route.Gateway.Compare(zero16) == 0 {
|
||||
route.Gateway = netaddr.IP{}
|
||||
}
|
||||
|
||||
if route.Source.Bits == 0 && (route.Source.IP.Compare(zero4) == 0 || route.Source.IP.Compare(zero16) == 0) {
|
||||
route.Source = netaddr.IPPrefix{}
|
||||
}
|
||||
|
||||
switch {
|
||||
case route.Gateway.IsZero():
|
||||
route.Scope = nethelpers.ScopeLink
|
||||
case route.Destination.IP.IsLinkLocalUnicast() || route.Destination.IP.IsLinkLocalMulticast():
|
||||
route.Scope = nethelpers.ScopeLink
|
||||
case route.Destination.IP.IsLoopback():
|
||||
route.Scope = nethelpers.ScopeHost
|
||||
default:
|
||||
route.Scope = nethelpers.ScopeGlobal
|
||||
}
|
||||
}
|
||||
|
||||
// NewRouteSpec initializes a RouteSpec resource.
|
||||
func NewRouteSpec(namespace resource.Namespace, id resource.ID) *RouteSpec {
|
||||
r := &RouteSpec{
|
||||
|
Loading…
x
Reference in New Issue
Block a user