284 lines
7.2 KiB
V
284 lines
7.2 KiB
V
// This file is part of netaddr.
|
|
//
|
|
// netaddr is free software: you can redistribute it and/or modify it under
|
|
// the terms of the GNU Lesser General Public License as published by the
|
|
// Free Software Foundation, either version 3 of the License, or (at your
|
|
// option) any later version.
|
|
//
|
|
// netaddr is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
// License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with netaddr. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
module netaddr
|
|
|
|
import encoding.binary
|
|
import math.bits
|
|
import rand
|
|
import rand.wyrand
|
|
|
|
pub struct Eui48 {
|
|
addr [6]u8
|
|
}
|
|
|
|
// Eui48.new creates new EUI-48 from six octets.
|
|
pub fn Eui48.new(a u8, b u8, c u8, d u8, e u8, f u8) Eui48 {
|
|
return Eui48{
|
|
addr: [a, b, c, d, e, f]!
|
|
}
|
|
}
|
|
|
|
// Eui48.from_octets creates new EUI-48 from six-element byte array.
|
|
pub fn Eui48.from_octets(addr [6]u8) Eui48 {
|
|
return Eui48{addr}
|
|
}
|
|
|
|
// Eui48.from_string parses addr string and returns new EUI-48 instance.
|
|
// Example:
|
|
// ```
|
|
// assert Eui48.from_string('a96:7a87:4ae3')!.str() == '0a-96-7a-87-4a-e3'
|
|
// ```
|
|
pub fn Eui48.from_string(addr string) !Eui48 {
|
|
mut bytes := [6]u8{}
|
|
match true {
|
|
addr.contains_any('-:') {
|
|
// canonical and unix formats
|
|
mac := addr.split_any('-:')
|
|
if mac.len == 6 {
|
|
for i := 0; i < 6; i++ {
|
|
if !('0x' + mac[i]).is_hex() {
|
|
return error('invalid octet in ${addr}')
|
|
}
|
|
bytes[i] = ('0x' + mac[i]).u8()
|
|
}
|
|
} else {
|
|
return error('6 octets expected in ${addr}')
|
|
}
|
|
}
|
|
addr.contains('.') {
|
|
// cisco triple-hextet format
|
|
mac := addr.split('.')
|
|
if mac.len == 3 {
|
|
mut i := 0
|
|
for part in mac {
|
|
if !('0x' + part).is_hex() {
|
|
return error('non-hexadecimal value in ${addr}')
|
|
}
|
|
pair := ('0x' + part).u8_array()
|
|
bytes[i] = pair[0]
|
|
bytes[i + 1] = pair[1]
|
|
i += 2
|
|
}
|
|
} else {
|
|
return error('3 hextets expected in ${addr}')
|
|
}
|
|
}
|
|
('0x' + addr).is_hex() {
|
|
// bare hex digit
|
|
mac := ('0x' + addr).u8_array()
|
|
len_diff := 6 - mac.len
|
|
if len_diff == 0 {
|
|
for i := 0; i < 6; i++ {
|
|
bytes[i] = mac[i]
|
|
}
|
|
} else if len_diff > 0 {
|
|
mut i := 0
|
|
for pos in len_diff .. 6 {
|
|
bytes[pos] = mac[i]
|
|
i++
|
|
}
|
|
} else {
|
|
return error('6 octets expected in ${addr}')
|
|
}
|
|
}
|
|
else {
|
|
return error('invalid EUI-48 in ${addr}')
|
|
}
|
|
}
|
|
return Eui48{bytes}
|
|
}
|
|
|
|
// Eui48.random is guaranteed to return a locally administered unicast EUI-48.
|
|
// By default the WyRandRNG is used with default seed. You can set custom OUI
|
|
// if you don't want generate random one.
|
|
// Example:
|
|
// ```v ignore
|
|
// >>> netaddr.Eui48.random()
|
|
// be-8c-f7-90-b4-60
|
|
// >>> netaddr.Eui48.random(oui: [u8(0x02), 0x0, 0x0]!)
|
|
// 02-00-00-2d-1d-01
|
|
// ```
|
|
pub fn Eui48.random(params Eui48RandomParams) Eui48 {
|
|
mut eui := [6]u8{}
|
|
mut prng := params.prng
|
|
if params.seed.len > 0 {
|
|
prng.seed(params.seed)
|
|
}
|
|
if params.oui != none {
|
|
eui[0], eui[1], eui[2] = params.oui[0], params.oui[1], params.oui[2]
|
|
} else {
|
|
eui[0], eui[1], eui[2] = prng.u8(), prng.u8(), prng.u8()
|
|
if (eui[0] >> 1) & 1 == 0 {
|
|
eui[0] ^= 0x02 // ensure to address is locally administreted
|
|
}
|
|
if eui[0] & 1 != 0 {
|
|
eui[0] &= ~1 // ensure to address is unicast
|
|
}
|
|
}
|
|
eui[3], eui[4], eui[5] = prng.u8(), prng.u8(), prng.u8()
|
|
return Eui48{eui}
|
|
}
|
|
|
|
// str returns EUI-48 string representation in canonical format.
|
|
pub fn (e Eui48) str() string {
|
|
return e.format(.canonical)
|
|
}
|
|
|
|
// format returns the MAC address as a string formatted according to the fmt rule.
|
|
pub fn (e Eui48) format(fmt Eui48Format) string {
|
|
mut mac := []string{}
|
|
match fmt {
|
|
.canonical {
|
|
for b in e.addr {
|
|
mac << b.hex()
|
|
}
|
|
return mac.join('-')
|
|
}
|
|
.unix {
|
|
for b in e.addr {
|
|
mac << b.hex()
|
|
}
|
|
return mac.join(':')
|
|
}
|
|
.hextets {
|
|
for i := 0; i <= 4; i += 2 {
|
|
mac << e.addr[i..i + 2].hex()
|
|
}
|
|
return mac.join('.')
|
|
}
|
|
.bare {
|
|
return e.addr[..].hex()
|
|
}
|
|
}
|
|
}
|
|
|
|
// u8_array returns EUI-48 as byte array.
|
|
pub fn (e Eui48) u8_array() []u8 {
|
|
return e.addr[..]
|
|
}
|
|
|
|
// u8_array_fixed returns EUI-48 as fixed size byte array.
|
|
pub fn (e Eui48) u8_array_fixed() [6]u8 {
|
|
return e.addr
|
|
}
|
|
|
|
// bit_len returns number of bits required to represent the current EUI-48.
|
|
pub fn (e Eui48) bit_len() int {
|
|
return bits.len_64(binary.big_endian_u64(e.addr[..]))
|
|
}
|
|
|
|
// oui_bytes returns the 24 bit Organizationally Unique Identifier (OUI) as byte array.
|
|
pub fn (e Eui48) oui_bytes() [3]u8 {
|
|
return [e.addr[0], e.addr[1], e.addr[2]]!
|
|
}
|
|
|
|
// ei_bytes returns the 24 bit Extended Identifier (EI) as byte array.
|
|
pub fn (e Eui48) ei_bytes() [3]u8 {
|
|
return [e.addr[3], e.addr[4], e.addr[5]]!
|
|
}
|
|
|
|
// eui64 returns the EUI-64 converted from EUI-48 via extending address with FF-FE bytes.
|
|
pub fn (e Eui48) eui64() Eui64 {
|
|
return Eui64{
|
|
addr: [e.addr[0], e.addr[1], e.addr[2], 0xff, 0xfe, e.addr[3], e.addr[4], e.addr[5]]!
|
|
}
|
|
}
|
|
|
|
// modified_eui64 converts the EUI-48 to Modified EUI-64.
|
|
// This is the same as `eui64()`, but the U/L-bit (universal/local bit) is inverted.
|
|
pub fn (e Eui48) modified_eui64() Eui64 {
|
|
return Eui64{
|
|
addr: [(e.addr[0] ^ 0x02), e.addr[1], e.addr[2], 0xff, 0xfe, e.addr[3], e.addr[4], e.addr[5]]!
|
|
}
|
|
}
|
|
|
|
// ipv6 creates new IPv6 address from EUI-48. EUI-48 will be converted to
|
|
// Modified EUI-64 and appended to network prefix. Byte-reversed `prefix` must fit in 64 bit.
|
|
pub fn (e Eui48) ipv6(prefix Ipv6Addr) !Ipv6Addr {
|
|
pref := prefix.u8_array_fixed()
|
|
eui64 := e.modified_eui64().u8_array_fixed()
|
|
if pref[8..] == []u8{len: 8} {
|
|
return Ipv6Addr.from_octets([
|
|
pref[0],
|
|
pref[1],
|
|
pref[2],
|
|
pref[3],
|
|
pref[4],
|
|
pref[5],
|
|
pref[6],
|
|
pref[7],
|
|
eui64[0],
|
|
eui64[1],
|
|
eui64[2],
|
|
eui64[3],
|
|
eui64[4],
|
|
eui64[5],
|
|
eui64[6],
|
|
eui64[7],
|
|
]!)!
|
|
}
|
|
return error('The prefix ${prefix} is too long. ' +
|
|
'At least 64 bits must remain for the interface identifier.')
|
|
}
|
|
|
|
// ipv6_link_local returns link-local IPv6 address created from EUI-48.
|
|
pub fn (e Eui48) ipv6_link_local() Ipv6Addr {
|
|
return e.ipv6(Ipv6Addr.new(0xfe80, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
|
|
0x0000) or { Ipv6Addr{} }) or { Ipv6Addr{} }
|
|
}
|
|
|
|
// is_universal returns true if address is universally administreted.
|
|
pub fn (e Eui48) is_universal() bool {
|
|
// U/L bit is 0
|
|
return (e.addr[0] >> 1) & 1 == 0
|
|
}
|
|
|
|
// is_local returns true if address is locally administreted.
|
|
pub fn (e Eui48) is_local() bool {
|
|
return !e.is_universal()
|
|
}
|
|
|
|
// is_multicast returns true if address is multicast.
|
|
pub fn (e Eui48) is_multicast() bool {
|
|
return !e.is_unicast()
|
|
}
|
|
|
|
// is_unicast returns true if address is unicast.
|
|
pub fn (e Eui48) is_unicast() bool {
|
|
// I/G bit is 0
|
|
return e.addr[0] & 1 == 0
|
|
}
|
|
|
|
// == returns true if a is equals b.
|
|
pub fn (a Eui48) == (b Eui48) bool {
|
|
return a.addr == b.addr
|
|
}
|
|
|
|
@[params]
|
|
pub struct Eui48RandomParams {
|
|
pub:
|
|
oui ?[3]u8 // the custom OUI which is used instead of the random one.
|
|
seed []u32 // seed for PRNG
|
|
prng rand.PRNG = wyrand.WyRandRNG{}
|
|
}
|
|
|
|
pub enum Eui48Format {
|
|
canonical // e.g. 0a-96-7a-87-4a-e3
|
|
unix // e.g. 0a:96:7a:87:4a:e3
|
|
hextets // e.g. 0a96.7a87.4ae3
|
|
bare // e.g. 0a967a874ae3
|
|
}
|