← Sections

Convert MAC Address to a IPv6 Link Local Address

The IPv6 Link Local Algorithm in Haskell

The OS should take care of this for you, but if you needed the algorithm in Haskell, here is how to make an 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:

Link-local_address

Online Converters:

-- prelude
import           Relude

-- base
import           Data.Bits

-- Hackage
import           Net.IPv6
import           Net.Mac

-- | Try and convert a MAC address to an IPv6 link local address
--
-- For example
--
-- @00:1c:a1:40:00:1f@ -> @fe80::21c:a1ff:fe40:1f@
mkIPV6LinkLocalAddress :: Text -> Maybe IPv6
mkIPV6LinkLocalAddress m = do
    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