packages feed

nettle-openflow-0.1: src/Examples/SimpleImperativeIONetworkControl/NetLearningSwitch.hs

{-# LANGUAGE RecordWildCards #-}

module Main where

import Nettle.Servers.MultiplexedTCPServer
import Nettle.Servers.TCPServer
import SimpleImperativeIONetworkControl
import Nettle.OpenFlow.Messages as M
import Nettle.OpenFlow.Switch
import Nettle.OpenFlow.Port
import Nettle.OpenFlow.Packet
import Nettle.OpenFlow.Action
import Nettle.OpenFlow.FlowTable
import Nettle.OpenFlow.Match
import Nettle.Ethernet.EthernetAddress
import Nettle.Ethernet.EthernetFrame
import Nettle.IPv4.IPPacket
import Control.Monad.State
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Binary
import Data.Binary.Get
import Control.Monad.Error


type ControlState = (HostTable, SwitchState)
type SwitchState = Map.Map SockAddr SwitchFeatures 

{- Functions for getting and modifying the switch state. -}
switchFeatures :: SockAddr -> NetController ControlState (Maybe SwitchFeatures)
switchFeatures addr = gets snd >>= return . Map.lookup addr

addSwitchFeatures :: SockAddr -> SwitchFeatures -> ControlState -> ControlState
addSwitchFeatures addr sfr (htbl,stbl) = (htbl, Map.insert addr sfr stbl)

deleteSwitch :: SockAddr -> ControlState -> ControlState
deleteSwitch addr (htbl,stbl) = (htbl, Map.delete addr stbl)


{- 
The host table mapps hosts to their known locations, i.e. to physical ports at OpenFlow switches. 
-}

type HostTable = Map.Map (EthernetAddress,SockAddr) PortID

newHostTable = Map.empty

learn ::  EthernetAddress -> SockAddr -> PortID -> HostTable -> HostTable
learn hostAddr switchAddr portID = Map.insert (hostAddr, switchAddr) portID

hostLookup ::  EthernetAddress -> SockAddr -> HostTable -> Maybe PortID
hostLookup hostAddr switchAddr htbl = Map.lookup (hostAddr, switchAddr) htbl

{- Convenience functions for manipulating state -}

hostTableM :: NetController ControlState HostTable
hostTableM = gets fst

learnM :: EthernetAddress -> SockAddr -> PortID -> NetController ControlState ()
learnM addr dpid inPort = hostTableM >>= updateHostTableM . learn addr dpid inPort 

hostLookupM :: EthernetAddress -> SockAddr -> NetController ControlState (Maybe PortID)
hostLookupM hostAddr switchAddr = hostTableM >>= return . hostLookup hostAddr switchAddr

updateHostTableM :: HostTable -> NetController ControlState ()
updateHostTableM htbl = modify (\(_,swState) -> (htbl,swState))



{- The controller -}

main = runNetController 2525 controller (newHostTable, Map.empty)

controller :: NetController ControlState ()
controller = mainHandler Set.empty
    where mainHandler needHellos = 
              do e <- waitForEvent 
                 case e of
                   ConnectionEstablished addr -> 
                       do sendMessage addr 0 CSHello
                          mainHandler (Set.insert addr needHellos)
                   ConnectionTerminated addr e -> 
                       do modify (deleteSwitch addr)
                          mainHandler (Set.delete addr needHellos)
                   PeerMessage addr (xid, msg) -> 
                       if addr `Set.member` needHellos
                       then case msg of 
                              SCHello -> do sendMessage addr 0 FeaturesRequest
                                            mainHandler (Set.delete addr needHellos) 
                              _       -> do liftIO $ putStrLn ("Expected to receive Hello from " ++ show addr ++ ", but received " ++ show msg)
                       else do case msg of 
                                 SCHello              -> liftIO $ putStrLn ("Received unexpected hello from " ++ show addr ++ ".")
                                 SCEchoRequest bytes  -> sendMessage addr 0 (CSEchoReply bytes)
                                 Features sfr         -> modify (addSwitchFeatures addr sfr)
                                 PacketIn pktInRecord -> handlePacketIn addr pktInRecord
                                 _                    -> liftIO $ putStrLn ("Unhandled message: " ++ show msg)
                               mainHandler needHellos

handlePacketIn addr pktIn@(PacketInfo {..}) = 
   case runGetE getEthernetFrame packetData of
    Left errorMessage -> liftIO $ putStrLn ("Failed to parse ethernet frame: " ++ errorMessage)
    Right ethernetFrame ->
      do learnM (sourceAddress ethernetFrame) addr receivedOnPort
         if isReserved (sourceAddress ethernetFrame)
           then sendMessage addr 0 $ M.FlowMod $ AddFlow { match             = frameToExactMatch receivedOnPort ethernetFrame
                                                         , actions           = []
                                                         , priority          = 0
                                                         , idleTimeOut       = ExpireAfter 60
                                                         , hardTimeOut       = Permanent
                                                         , applyToPacket     = bufferID
                                                         , overlapAllowed    = True
                                                         , notifyWhenRemoved = False
                                                         , cookie            = 0
                                                         }
           else do mport <- hostLookupM (destAddress ethernetFrame) addr
                   case mport of 
                     Nothing   -> sendMessage addr 0 (M.PacketOut $ receivedPacketOut pktIn flood)
                     Just lprt -> do liftIO $ putStrLn $ "at addr: " ++ show addr ++ " forward to port " ++ show lprt
                                     sendMessage addr 0 $ M.FlowMod $ AddFlow { match             = frameToExactMatch receivedOnPort ethernetFrame
                                                                              , actions           = sendOnPort lprt
                                                                              , priority          = 0
                                                                              , idleTimeOut       = ExpireAfter 60
                                                                              , hardTimeOut       = Permanent
                                                                              , applyToPacket     = bufferID
                                                                              , overlapAllowed    = True
                                                                              , notifyWhenRemoved = False
                                                                              , cookie            = 0
                                                                              }
                                     sendMessage addr 0 $ M.PacketOut $ receivedPacketOut pktIn (sendOnPort lprt)