Authored: 2021-10-11;
categories : programming;

The OS should take care of this for you, but if you needed the algorithm in Haskell, here is how to make a IPv6 link local address from a MAC address. Requires the relude and ip packages. Although it's trivial to drop the `Relude` requirement in favor of `Prelude`.

References:

Online Converters:

``````-- prelude
import           Relude

-- base
import           Data.Bits

-- Hackage
import           Net.IPv6
import           Net.Mac

--
-- For example
--
-- @00:1c:a1:40:00:1f@ -> @fe80::21c:a1ff:fe40:1f@
vm_mac <- Net.Mac.decode m
let (o1, o2, o3, o6, o7, o8) = Net.Mac.toOctets vm_mac

o1_inverted <- invertAt 6 (toBinary o1)

let o1'  = toDecimal o1_inverted
s5   = toWord16 (fromIntegral o1') (fromIntegral o2)
s6   = toWord16 (fromIntegral o3) 0xff
s7   = toWord16 0xfe (fromIntegral o6)
s8   = toWord16 (fromIntegral o7) (fromIntegral o8)

Just (Net.IPv6.fromWord16s 0xfe80 0x0 0x0 0x0 s5 s6 s7 s8)

where
toWord16 :: Word16 -> Word16 -> Word16
toWord16 hi lo =
let hi' = Data.Bits.shift hi 8
lo' = (.&.) lo 0xff
in  (.|.) hi' lo'

toDecimal :: Foldable t => t Word8 -> Word8
toDecimal n = snd (foldr f (0, 0) n)
where
f :: Word8 -> (Word8, Word8) -> (Word8, Word8)
f a (i, b) = (i + 1, b + (a * (2 ^ i)))

invertAt :: Int -> [Word8] -> Maybe [Word8]
invertAt i n = case n !!? i of
Nothing -> Nothing
Just e  -> do
let (start, end) = splitAt i n
case viaNonEmpty tail end of
Nothing          -> Nothing
Just end_dropped -> Just (start ++ [invert e] ++ end_dropped)
where
invert 0  = 1
invert 1  = 0
invert i' = i'

toBinary :: Integral a => a -> [a]
toBinary 0 = replicate 8 0
toBinary n = pad (reverse (helper n))
where
pad n' = replicate (8 - length n') 0 ++ n'

helper 0  = []
helper n' = let (q, r) = n' `divMod` 2 in r : helper q
``````