adamflott.com

Convert MAC Address to a IPv6 Link Local Address

Description: The IPv6 Link Local Algorithm in Haskell
Authored: 2021-10-11;
Permalink: https://adamflott.com/programming/haskell/convert-mac-to-ipv6-link-local/
categories : programming;
tags : haskell; ipv6; networking;


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

-- | 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