@@ -19,12 +19,15 @@ use nexus_db_schema::schema::service_network_interface;
1919use nexus_sled_agent_shared:: inventory:: ZoneKind ;
2020use nexus_types:: external_api:: params;
2121use nexus_types:: identity:: Resource ;
22- use omicron_common:: api:: external:: Error ;
2322use omicron_common:: api:: { external, internal} ;
2423use omicron_uuid_kinds:: GenericUuid ;
2524use omicron_uuid_kinds:: InstanceUuid ;
2625use omicron_uuid_kinds:: OmicronZoneUuid ;
2726use omicron_uuid_kinds:: VnicUuid ;
27+ use oxnet:: IpNet ;
28+ use oxnet:: Ipv4Net ;
29+ use oxnet:: Ipv6Net ;
30+ use std:: net:: IpAddr ;
2831use uuid:: Uuid ;
2932
3033/// The max number of interfaces that may be associated with a resource,
@@ -346,6 +349,179 @@ impl From<ServiceNetworkInterface> for NetworkInterface {
346349 }
347350}
348351
352+ mod private {
353+ pub trait IpSealed : Clone + Copy + std:: fmt:: Debug {
354+ fn into_ipnet ( self ) -> ipnetwork:: IpNetwork ;
355+ }
356+
357+ impl IpSealed for std:: net:: Ipv4Addr {
358+ fn into_ipnet ( self ) -> ipnetwork:: IpNetwork {
359+ ipnetwork:: IpNetwork :: V4 ( ipnetwork:: Ipv4Network :: from ( self ) )
360+ }
361+ }
362+ impl IpSealed for std:: net:: Ipv6Addr {
363+ fn into_ipnet ( self ) -> ipnetwork:: IpNetwork {
364+ ipnetwork:: IpNetwork :: V6 ( ipnetwork:: Ipv6Network :: from ( self ) )
365+ }
366+ }
367+ }
368+
369+ pub trait Ip : private:: IpSealed { }
370+ impl < T > Ip for T where T : private:: IpSealed { }
371+
372+ /// How an IP address is assigned to an interface.
373+ #[ derive( Clone , Copy , Debug , Default ) ]
374+ pub enum IpAssignment < T : Ip > {
375+ /// Automatically assign an IP address.
376+ #[ default]
377+ Auto ,
378+ /// Explicitly assign a specific address, if available.
379+ Explicit ( T ) ,
380+ }
381+
382+ /// How to assign an IPv4 address.
383+ pub type Ipv4Assignment = IpAssignment < std:: net:: Ipv4Addr > ;
384+
385+ /// How to assign an IPv6 address.
386+ pub type Ipv6Assignment = IpAssignment < std:: net:: Ipv6Addr > ;
387+
388+ /// Configuration for a network interface's IPv4 addressing.
389+ #[ derive( Clone , Debug , Default ) ]
390+ pub struct Ipv4Config {
391+ /// The VPC-private address to assign to the interface.
392+ pub ip : Ipv4Assignment ,
393+ /// Additional IP networks the interface can send / receive on.
394+ pub transit_ips : Vec < Ipv4Net > ,
395+ }
396+
397+ /// Configuration for a network interface's IPv6 addressing.
398+ #[ derive( Clone , Debug , Default ) ]
399+ pub struct Ipv6Config {
400+ /// The VPC-private address to assign to the interface.
401+ pub ip : Ipv6Assignment ,
402+ /// Additional IP networks the interface can send / receive on.
403+ pub transit_ips : Vec < Ipv6Net > ,
404+ }
405+
406+ /// Configuration for a network interface's IP addressing.
407+ #[ derive( Clone , Debug ) ]
408+ pub enum IpConfig {
409+ /// The interface has only an IPv4 stack.
410+ V4 ( Ipv4Config ) ,
411+ /// The interface has only an IPv6 stack.
412+ V6 ( Ipv6Config ) ,
413+ /// The interface has both an IPv4 and IPv6 stack.
414+ DualStack { v4 : Ipv4Config , v6 : Ipv6Config } ,
415+ }
416+
417+ impl IpConfig {
418+ /// Construct an IPv4 configuration with no transit IPs.
419+ pub fn from_ipv4 ( addr : std:: net:: Ipv4Addr ) -> Self {
420+ IpConfig :: V4 ( Ipv4Config {
421+ ip : Ipv4Assignment :: Explicit ( addr) ,
422+ transit_ips : vec ! [ ] ,
423+ } )
424+ }
425+
426+ /// Construct an IP configuration with only an automatic IPv4 address.
427+ pub fn auto_ipv4 ( ) -> Self {
428+ IpConfig :: V4 ( Ipv4Config :: default ( ) )
429+ }
430+
431+ /// Return the IPv4 address assignment.
432+ pub fn ipv4_assignment ( & self ) -> Option < & Ipv4Assignment > {
433+ match self {
434+ IpConfig :: V4 ( Ipv4Config { ip, .. } ) => Some ( ip) ,
435+ IpConfig :: V6 ( _) => None ,
436+ IpConfig :: DualStack { v4 : Ipv4Config { ip, .. } , .. } => Some ( ip) ,
437+ }
438+ }
439+
440+ /// Return the IPv4 address explicitly requested, if one exists.
441+ pub fn ipv4_addr ( & self ) -> Option < & std:: net:: Ipv4Addr > {
442+ self . ipv4_assignment ( ) . and_then ( |assignment| match assignment {
443+ IpAssignment :: Auto => None ,
444+ IpAssignment :: Explicit ( addr) => Some ( addr) ,
445+ } )
446+ }
447+
448+ /// Construct an IPv6 configuration with no transit IPs.
449+ pub fn from_ipv6 ( addr : std:: net:: Ipv6Addr ) -> Self {
450+ IpConfig :: V6 ( Ipv6Config {
451+ ip : Ipv6Assignment :: Explicit ( addr) ,
452+ transit_ips : vec ! [ ] ,
453+ } )
454+ }
455+
456+ /// Construct an IP configuration with only an automatic IPv6 address.
457+ pub fn auto_ipv6 ( ) -> Self {
458+ IpConfig :: V6 ( Ipv6Config :: default ( ) )
459+ }
460+
461+ /// Return the IPv6 address assignment.
462+ pub fn ipv6_assignment ( & self ) -> Option < & Ipv6Assignment > {
463+ match self {
464+ IpConfig :: V6 ( Ipv6Config { ip, .. } ) => Some ( ip) ,
465+ IpConfig :: V4 ( _) => None ,
466+ IpConfig :: DualStack { v6 : Ipv6Config { ip, .. } , .. } => Some ( ip) ,
467+ }
468+ }
469+
470+ /// Return the IPv6 address explicitly requested, if one exists.
471+ pub fn ipv6_addr ( & self ) -> Option < & std:: net:: Ipv6Addr > {
472+ self . ipv6_assignment ( ) . and_then ( |assignment| match assignment {
473+ IpAssignment :: Auto => None ,
474+ IpAssignment :: Explicit ( addr) => Some ( addr) ,
475+ } )
476+ }
477+
478+ /// Return the transit IPs requested in this configuration.
479+ pub fn transit_ips ( & self ) -> Vec < IpNet > {
480+ match self {
481+ IpConfig :: V4 ( Ipv4Config { transit_ips, .. } ) => {
482+ transit_ips. iter ( ) . copied ( ) . map ( Into :: into) . collect ( )
483+ }
484+ IpConfig :: V6 ( Ipv6Config { transit_ips, .. } ) => {
485+ transit_ips. iter ( ) . copied ( ) . map ( Into :: into) . collect ( )
486+ }
487+ IpConfig :: DualStack {
488+ v4 : Ipv4Config { transit_ips : ipv4_addrs, .. } ,
489+ v6 : Ipv6Config { transit_ips : ipv6_addrs, .. } ,
490+ } => ipv4_addrs
491+ . iter ( )
492+ . copied ( )
493+ . map ( Into :: into)
494+ . chain ( ipv6_addrs. iter ( ) . copied ( ) . map ( Into :: into) )
495+ . collect ( ) ,
496+ }
497+ }
498+
499+ /// Construct an IP configuration with both IPv4 / IPv6 addresses and no
500+ /// transit IPs.
501+ pub fn auto_dual_stack ( ) -> Self {
502+ IpConfig :: DualStack {
503+ v4 : Ipv4Config :: default ( ) ,
504+ v6 : Ipv6Config :: default ( ) ,
505+ }
506+ }
507+
508+ /// Return true if this config has any transit IPs
509+ fn has_transit_ips ( & self ) -> bool {
510+ match self {
511+ IpConfig :: V4 ( Ipv4Config { transit_ips, .. } ) => {
512+ !transit_ips. is_empty ( )
513+ }
514+ IpConfig :: V6 ( Ipv6Config { transit_ips, .. } ) => {
515+ !transit_ips. is_empty ( )
516+ }
517+ IpConfig :: DualStack {
518+ v4 : Ipv4Config { transit_ips : ipv4_addrs, .. } ,
519+ v6 : Ipv6Config { transit_ips : ipv6_addrs, .. } ,
520+ } => !ipv4_addrs. is_empty ( ) || !ipv6_addrs. is_empty ( ) ,
521+ }
522+ }
523+ }
524+
349525/// A not fully constructed NetworkInterface. It may not yet have an IP
350526/// address allocated.
351527#[ derive( Clone , Debug ) ]
@@ -354,10 +530,9 @@ pub struct IncompleteNetworkInterface {
354530 pub kind : NetworkInterfaceKind ,
355531 pub parent_id : Uuid ,
356532 pub subnet : VpcSubnet ,
357- pub ip : Option < std :: net :: IpAddr > ,
533+ pub ip_config : IpConfig ,
358534 pub mac : Option < external:: MacAddr > ,
359535 pub slot : Option < u8 > ,
360- pub transit_ips : Vec < IpNetwork > ,
361536}
362537
363538impl IncompleteNetworkInterface {
@@ -368,20 +543,15 @@ impl IncompleteNetworkInterface {
368543 parent_id : Uuid ,
369544 subnet : VpcSubnet ,
370545 identity : external:: IdentityMetadataCreateParams ,
371- ip : Option < std :: net :: IpAddr > ,
546+ ip_config : IpConfig ,
372547 mac : Option < external:: MacAddr > ,
373548 slot : Option < u8 > ,
374- transit_ips : Vec < IpNetwork > ,
375549 ) -> Result < Self , external:: Error > {
376- if let Some ( ip) = ip {
377- // TODO-completeness:
378- // https://github.com/oxidecomputer/omicron/issues/9244.
379- if ip. is_ipv6 ( ) {
380- return Err ( Error :: invalid_request (
381- "IPv6 addresses are not yet supported" ,
382- ) ) ;
383- }
384- subnet. check_requestable_addr ( ip) ?;
550+ if let Some ( ip) = ip_config. ipv4_addr ( ) {
551+ subnet. check_requestable_addr ( IpAddr :: V4 ( * ip) ) ?;
552+ } ;
553+ if let Some ( ip) = ip_config. ipv6_addr ( ) {
554+ subnet. check_requestable_addr ( IpAddr :: V6 ( * ip) ) ?;
385555 } ;
386556 if let Some ( mac) = mac {
387557 match kind {
@@ -422,10 +592,9 @@ impl IncompleteNetworkInterface {
422592 kind,
423593 parent_id,
424594 subnet,
425- ip ,
595+ ip_config ,
426596 mac,
427597 slot,
428- transit_ips,
429598 } )
430599 }
431600
@@ -434,19 +603,17 @@ impl IncompleteNetworkInterface {
434603 instance_id : InstanceUuid ,
435604 subnet : VpcSubnet ,
436605 identity : external:: IdentityMetadataCreateParams ,
437- ip : Option < std:: net:: IpAddr > ,
438- transit_ips : Vec < IpNetwork > ,
606+ ip_config : IpConfig ,
439607 ) -> Result < Self , external:: Error > {
440608 Self :: new (
441609 interface_id,
442610 NetworkInterfaceKind :: Instance ,
443611 instance_id. into_untyped_uuid ( ) ,
444612 subnet,
445613 identity,
446- ip ,
614+ ip_config ,
447615 None ,
448616 None ,
449- transit_ips,
450617 )
451618 }
452619
@@ -455,20 +622,24 @@ impl IncompleteNetworkInterface {
455622 service_id : Uuid ,
456623 subnet : VpcSubnet ,
457624 identity : external:: IdentityMetadataCreateParams ,
458- ip : std :: net :: IpAddr ,
625+ ip_config : IpConfig ,
459626 mac : external:: MacAddr ,
460627 slot : u8 ,
461628 ) -> Result < Self , external:: Error > {
629+ if ip_config. has_transit_ips ( ) {
630+ return Err ( external:: Error :: invalid_request (
631+ "Cannot specify transit IPs for service NICs" ,
632+ ) ) ;
633+ }
462634 Self :: new (
463635 interface_id,
464636 NetworkInterfaceKind :: Service ,
465637 service_id,
466638 subnet,
467639 identity,
468- Some ( ip ) ,
640+ ip_config ,
469641 Some ( mac) ,
470642 Some ( slot) ,
471- vec ! [ ] , // Service interfaces don't use transit_ips
472643 )
473644 }
474645
@@ -477,7 +648,7 @@ impl IncompleteNetworkInterface {
477648 probe_id : Uuid ,
478649 subnet : VpcSubnet ,
479650 identity : external:: IdentityMetadataCreateParams ,
480- ip : Option < std :: net :: IpAddr > ,
651+ ip_config : IpConfig ,
481652 mac : Option < external:: MacAddr > ,
482653 ) -> Result < Self , external:: Error > {
483654 Self :: new (
@@ -486,10 +657,9 @@ impl IncompleteNetworkInterface {
486657 probe_id,
487658 subnet,
488659 identity,
489- ip ,
660+ ip_config ,
490661 mac,
491662 None ,
492- vec ! [ ] , // Probe interfaces don't use transit_ips
493663 )
494664 }
495665}
0 commit comments