@@ -552,15 +552,25 @@ pub struct MacAddr(pub external::MacAddr);
552552impl MacAddr {
553553 /// Generate a unique MAC address for an interface
554554 pub fn new ( ) -> Result < Self , external:: Error > {
555- use rand:: Fill ;
556- // Use the Oxide OUI A8 40 25
557- let mut addr = [ 0xA8 , 0x40 , 0x25 , 0x00 , 0x00 , 0x00 ] ;
558- addr[ 3 ..] . try_fill ( & mut StdRng :: from_entropy ( ) ) . map_err ( |_| {
559- external:: Error :: internal_error ( "failed to generate MAC" )
560- } ) ?;
561- // From RFD 174, Oxide virtual MACs are constrained to have these bits
562- // set.
563- addr[ 3 ] |= 0xF0 ;
555+ // From RFD 174, the last three octets of Oxide virtual MACs are
556+ // constrained to be in the range F0:00:00 - FF:FF:FF. However, we're
557+ // further splitting this into a range for customer instances, F0:00:00
558+ // - EF:FF:FF, and VPC / system MAC addresses, FF:00:00 - FF:FF:FF.
559+ // See
560+ // https://github.com/oxidecomputer/omicron/pull/955#discussion_r856432498
561+ // for the initial discussion surrounding this split.
562+ //
563+ // TODO-completeness: Update this comment point to the relevant RFD(s)
564+ // once they're created.
565+ use rand:: Rng ;
566+ const MIN : u32 = 0x00_00_00 ;
567+ const MAX : u32 = 0x0E_FF_FF ;
568+ let bytes = rand:: thread_rng ( ) . gen_range ( MIN ..=MAX ) . to_be_bytes ( ) ;
569+
570+ // Use the Oxide OUI A8 40 25, and set the first nibble of the third
571+ // byte, to ensure we're in the virtual address range.
572+ let addr = [ 0xA8 , 0x40 , 0x25 , 0xF0 | bytes[ 1 ] , bytes[ 2 ] , bytes[ 3 ] ] ;
573+
564574 // TODO-correctness: We should use an explicit allocator for the MACs
565575 // given the small address space. Right now creation requests may fail
566576 // due to MAC collision, especially given the 20-bit space.
@@ -1848,6 +1858,35 @@ impl OximeterInfo {
18481858 }
18491859}
18501860
1861+ #[ derive( Clone , Debug , Copy , AsExpression , FromSqlRow ) ]
1862+ #[ diesel( sql_type = sql_types:: Int4 ) ]
1863+ pub struct Vni ( pub external:: Vni ) ;
1864+
1865+ impl < DB > ToSql < sql_types:: Int4 , DB > for Vni
1866+ where
1867+ DB : Backend < BindCollector = diesel:: query_builder:: bind_collector:: RawBytesBindCollector < DB > > ,
1868+ i32 : ToSql < sql_types:: Int4 , DB > ,
1869+ {
1870+ fn to_sql < ' b > (
1871+ & ' b self ,
1872+ out : & mut serialize:: Output < ' b , ' _ , DB > ,
1873+ ) -> serialize:: Result {
1874+ // Reborrowing is necessary to ensure that the lifetime of the temporary
1875+ // i32 created here and `out` is the same, i.e., that `'b = '_`.
1876+ i32:: try_from ( u32:: from ( self . 0 ) ) . unwrap ( ) . to_sql ( & mut out. reborrow ( ) )
1877+ }
1878+ }
1879+
1880+ impl < DB > FromSql < sql_types:: Int4 , DB > for Vni
1881+ where
1882+ DB : Backend ,
1883+ i32 : FromSql < sql_types:: Int4 , DB > ,
1884+ {
1885+ fn from_sql ( bytes : RawValue < DB > ) -> deserialize:: Result < Self > {
1886+ Ok ( Vni ( external:: Vni :: try_from ( i32:: from_sql ( bytes) ?) ?) )
1887+ }
1888+ }
1889+
18511890#[ derive( Queryable , Insertable , Clone , Debug , Selectable , Resource ) ]
18521891#[ diesel( table_name = vpc) ]
18531892pub struct Vpc {
@@ -1856,6 +1895,7 @@ pub struct Vpc {
18561895
18571896 pub project_id : Uuid ,
18581897 pub system_router_id : Uuid ,
1898+ pub vni : Vni ,
18591899 pub ipv6_prefix : Ipv6Net ,
18601900 pub dns_name : Name ,
18611901
@@ -1890,6 +1930,7 @@ impl Vpc {
18901930 identity,
18911931 project_id,
18921932 system_router_id,
1933+ vni : Vni ( external:: Vni :: random ( ) ) ,
18931934 ipv6_prefix,
18941935 dns_name : params. dns_name . into ( ) ,
18951936 firewall_gen : Generation :: new ( ) ,
@@ -2709,6 +2750,7 @@ impl UpdateAvailableArtifact {
27092750
27102751#[ cfg( test) ]
27112752mod tests {
2753+ use super :: MacAddr ;
27122754 use super :: Uuid ;
27132755 use super :: VpcSubnet ;
27142756 use ipnetwork:: Ipv4Network ;
@@ -2830,4 +2872,19 @@ mod tests {
28302872 assert ! ( !subnet. check_requestable_addr( "fd00::1" . parse( ) . unwrap( ) ) ) ;
28312873 assert ! ( subnet. check_requestable_addr( "fd00::1:1" . parse( ) . unwrap( ) ) ) ;
28322874 }
2875+
2876+ #[ test]
2877+ fn test_random_mac_address ( ) {
2878+ let mac = MacAddr :: new ( ) . expect ( "Failed to create random MAC" ) ;
2879+ let bytes = mac. 0 . as_bytes ( ) ;
2880+ assert_eq ! ( & bytes[ ..3 ] , & [ 0xA8 , 0x40 , 0x25 ] , "Invalid Oxide OUI" ) ;
2881+ assert_eq ! (
2882+ bytes[ 3 ] & 0xF0 ,
2883+ 0xF0 ,
2884+ concat!(
2885+ "Customer MAC addresses should be in the virtual range " ,
2886+ "a8:40:25:f0:00:00 - a8:40:25:fe:ff:ff"
2887+ )
2888+ ) ;
2889+ }
28332890}
0 commit comments