44 "context"
55 "encoding/json"
66 "fmt"
7- "net"
7+ "net/netip "
88 "slices"
9+ "strings"
910 "sync"
1011
1112 ctlkubevirtv1 "github.com/harvester/harvester/pkg/generated/controllers/kubevirt.io/v1"
@@ -70,7 +71,10 @@ func (i *instanceManager) InstanceMetadata(ctx context.Context, node *v1.Node) (
7071 meta .Zone = zone
7172 }
7273
73- meta .NodeAddresses = getNodeAddresses (node , vmi )
74+ meta .NodeAddresses , err = getNodeAddresses (node , vmi )
75+ if err != nil {
76+ return nil , err
77+ }
7478
7579 return meta , nil
7680}
@@ -84,46 +88,138 @@ func (i *instanceManager) getVM(node *v1.Node) (*kubevirtv1.VirtualMachine, erro
8488}
8589
8690// getNodeAddresses return nodeAddresses only when the value of annotation `alpha.kubernetes.io/provided-node-ip` is not empty
87- func getNodeAddresses (node * v1.Node , vmi * kubevirtv1.VirtualMachineInstance ) []v1.NodeAddress {
88- providedNodeIP , ok := node .Annotations [api .AnnotationAlphaProvidedIPAddr ]
89- if ! ok {
90- return nil
91- }
92-
93- aiIPs , err := getAdditionalInternalIPs (node )
91+ func getNodeAddresses (node * v1.Node , vmi * kubevirtv1.VirtualMachineInstance ) ([]v1.NodeAddress , error ) {
92+ internalIPRanges , err := getInternalIPRanges (node )
9493 if err != nil {
95- // if additional IPs are not correctly marked, only log an error, do not return this error
96- logrus .WithFields (logrus.Fields {
97- "namespace" : node .Namespace ,
98- "name" : node .Name ,
99- }).Debugf ("%s, skip it" , err .Error ())
94+ return nil , err
10095 }
10196
102- nodeAddresses := make ([]v1.NodeAddress , 0 , len (vmi .Spec .Networks )+ 1 )
97+ // Optimistically assume that for every interface have one IP. Add one for the hostname address that we add later.
98+ // Since the amount of IP addresses is probably very limited this should be fine.
99+ nodeAddresses := make ([]v1.NodeAddress , 0 , len (vmi .Status .Interfaces )+ 1 )
103100
101+ // Build a list of network names (names of NICs) on the VM.
102+ networkNames := make ([]string , 0 , len (vmi .Spec .Networks ))
104103 for _ , network := range vmi .Spec .Networks {
105- for _ , networkInterface := range vmi .Status .Interfaces {
106- if network .Name == networkInterface .Name {
107- if ip := net .ParseIP (networkInterface .IP ); ip != nil && ip .To4 () != nil {
108- nodeAddr := v1.NodeAddress {
109- Address : networkInterface .IP ,
110- }
111- if networkInterface .IP == providedNodeIP || (aiIPs != nil && slices .Contains (aiIPs , networkInterface .IP )) {
112- nodeAddr .Type = v1 .NodeInternalIP
113- } else {
114- nodeAddr .Type = v1 .NodeExternalIP
115- }
116- nodeAddresses = append (nodeAddresses , nodeAddr )
104+ networkNames = append (networkNames , network .Name )
105+ }
106+
107+ // Find all IP addresses of the VM
108+ for _ , networkInterface := range vmi .Status .Interfaces {
109+ // The interface list might contain interfaces that do not belong to any NIC of the VM. Filter them out.
110+ if ! slices .Contains (networkNames , networkInterface .Name ) {
111+ // Ignore interface since it does not belong to one of the NICs.
112+ continue
113+ }
114+
115+ for _ , ipStr := range networkInterface .IPs {
116+ ip , err := netip .ParseAddr (ipStr )
117+ if err != nil {
118+ // Failed to parse IP, skip it
119+ logrus .WithFields (logrus.Fields {
120+ "namespace" : node .Namespace ,
121+ "name" : node .Name ,
122+ }).Warnf ("Unable to parse IP %s, skip it: %s" , ipStr , err .Error ())
123+ continue
124+ }
125+
126+ // Determine if the IP should be listed as an internal or external IP.
127+ ipType := v1 .NodeExternalIP
128+ for _ , internalPrefix := range internalIPRanges {
129+ if internalPrefix .Contains (ip ) {
130+ // IP is an internal IP, no need to check further.
131+ ipType = v1 .NodeInternalIP
132+ break
117133 }
118134 }
135+
136+ nodeAddresses = append (nodeAddresses , v1.NodeAddress {
137+ Type : ipType ,
138+ Address : ip .String (),
139+ })
119140 }
120141 }
142+
121143 nodeAddresses = append (nodeAddresses , v1.NodeAddress {
122144 Type : v1 .NodeHostName ,
123145 Address : node .Name ,
124146 })
125147
126- return nodeAddresses
148+ return nodeAddresses , nil
149+ }
150+
151+ func getInternalIPRanges (node * v1.Node ) ([]netip.Prefix , error ) {
152+ internalIPRanges := make ([]netip.Prefix , 0 , 1 ) // Most of the time we would only have 1 internal range defined, the provided node IP
153+
154+ // Kubelet sets this node annotation if the --node-ip flag is set and an external cloud provider is used
155+ providedNodeIP , ok := node .Annotations [api .AnnotationAlphaProvidedIPAddr ]
156+ if ! ok {
157+ // Annotation is not set, this could be because we are running in a dual stack setup.
158+ // Assume all IPs are internal IPs.
159+ internalIPRanges = append (internalIPRanges , netip .MustParsePrefix ("0.0.0.0/0" ))
160+ internalIPRanges = append (internalIPRanges , netip .MustParsePrefix ("::/0" ))
161+ return internalIPRanges , nil
162+ }
163+
164+ // We got an IP from kubelet, parse it and convert it to a prefix containing only this IP
165+ nodeIPRange , err := ipStringToPrefix (providedNodeIP )
166+ if err != nil {
167+ return nil , fmt .Errorf ("annotation \" %s\" is invalid: %w" , api .AnnotationAlphaProvidedIPAddr , err )
168+ }
169+ internalIPRanges = append (internalIPRanges , nodeIPRange )
170+
171+ // Support marking extra IPs as internal
172+ extraInternalIPs , err := getAdditionalInternalIPs (node )
173+ if err != nil {
174+ // Unable to parse extra provided internal IP ranges, ignore them.
175+ logrus .WithFields (logrus.Fields {
176+ "namespace" : node .Namespace ,
177+ "name" : node .Name ,
178+ }).Warnf ("%s, skip it" , err .Error ())
179+
180+ // Return list without extra user defined IP ranges.
181+ return internalIPRanges , nil
182+ }
183+
184+ for _ , extraInternalIP := range extraInternalIPs {
185+ extraRange , err := ipStringToPrefix (extraInternalIP )
186+ if err != nil {
187+ // IP (range) malformed, skip it.
188+ logrus .WithFields (logrus.Fields {
189+ "namespace" : node .Namespace ,
190+ "name" : node .Name ,
191+ }).Warnf ("Unable to parse IP %s, skip it: %s" , extraInternalIP , err .Error ())
192+ continue
193+ }
194+ internalIPRanges = append (internalIPRanges , extraRange )
195+ }
196+
197+ return internalIPRanges , nil
198+ }
199+
200+ // ipStringToPrefix converts an IP / CIDR range to a netip.Prefix. It supports IPv4 and IPv6 addresses.
201+ // If a plain IP address is given, it returns a Prefix that only contains this IP.
202+ // If a CIDR range is given, it returns a Prefix that contains the whole range.
203+ func ipStringToPrefix (str string ) (netip.Prefix , error ) {
204+ if strings .Contains (str , "/" ) {
205+ // CIDR notation
206+ return netip .ParsePrefix (str )
207+ }
208+
209+ // Plain IP address
210+ addr , err := netip .ParseAddr (str )
211+ if err != nil {
212+ return netip.Prefix {}, fmt .Errorf ("failed to parse IP address \" %s\" : %w" , str , err )
213+ }
214+
215+ // For a single IPv4 address, the prefix length is 32; for IPv6, it's 128.
216+ prefixLen := 32
217+ if addr .Is6 () {
218+ prefixLen = 128
219+ }
220+
221+ // Create a prefix with the single address in it.
222+ return addr .Prefix (prefixLen )
127223}
128224
129225// User may want to mark some IPs of the node also as internal
0 commit comments