nettle-frp-0.1: src/Nettle/FRPControl/SwitchInterface.hs
{-# LANGUAGE Arrows, CPP #-}
-- | This module defines type classes for input types carrying
-- switch messages and output types carrying switch commands. It defines
-- a few basic instances of these classes. In addition it defines
-- some signal functions that implement the most basic functionality to maintain
-- switch-controller connections.
module Nettle.FRPControl.SwitchInterface
(
-- * Switch input
SwitchMessage
, SockAddr
, switchJoinE
, switchLeaveE
, switchHelloE
, switchEchoRequestE
, switchEchoReplyE
, packetInE
, switchFeaturesE
, portUpdateE
, switchErrorE
, flowRemovalE
, statsReplyE
, portStatsReplyE
, flowStatsReplyE
#if OPENFLOW_VERSION==152 || OPENFLOW_VERSION==1
, barrierReplyE
#endif
, filterPacketIns
-- * Switch output
, SwitchOutput
, switchCommand
, noOp
, (<+>)
-- * Driver
, switchInterfaceDriver
-- * Basic OpenFlow controller
, openFlowController
) where
import Nettle.Ethernet.EthernetAddress
import Nettle.OpenFlow.Messages as M
import Nettle.OpenFlow.Switch
import Nettle.OpenFlow.Port
import Nettle.OpenFlow.Action
import Nettle.OpenFlow.Match
import Nettle.OpenFlow.FlowTable
import Nettle.OpenFlow.Statistics
import Nettle.OpenFlow.Error
import Nettle.OpenFlow.Packet
import Nettle.OpenFlow.MessagesBinary as M
import Nettle.Servers.TCPServer
import Nettle.Servers.MultiplexedTCPServer
import Nettle.FRPControl.AFRP
import Data.Word
import Data.Monoid
import Control.Exception
-- | A switch message is a TCP message where normal messages
-- contain a @TransactionID@ and an @SCMessage@
type SwitchMessage = TCPMessage (TransactionID, SCMessage)
-- | Projects out the @SockAddr@ associated with the switch from a @ConnectionEstablished@ @TCPMessage@.
switchJoinE :: Event SwitchMessage -> Event SockAddr
switchJoinE = mapFilterE f
where f (ConnectionEstablished addr) = Just addr
f _ = Nothing
-- | Outputs an event whenever the switch disconnects from the controller.
-- The event carries the @SockAddr@ used to communicate with the switch and
-- an @IOException@ value indicating the reason for the disconnection.
switchLeaveE :: Event SwitchMessage -> Event (SockAddr, IOException)
switchLeaveE = mapFilterE f
where f (ConnectionTerminated addr e) = Just (addr, e)
f _ = Nothing
-- | Outputs an event whenever a switch sends a hello message.
switchHelloE :: Event SwitchMessage -> Event (SockAddr, TransactionID)
switchHelloE = mapFilterE f
where f (PeerMessage sw (msgID, SCHello)) = Just (sw, msgID)
f _ = Nothing
-- | Outputs an event whenever a switch sends an echo request; carries
-- the @SockAddr@ of the switch, the @TransactionID@ of the echo request,
-- and the data included in the echo request.
switchEchoRequestE :: Event SwitchMessage -> Event (SockAddr, TransactionID, [Word8])
switchEchoRequestE = mapFilterE f
where f (PeerMessage sw (msgID, SCEchoRequest bytes)) = Just (sw, msgID, bytes)
f _ = Nothing
-- | Outputs an event whenever a switch sends an echo reply; carries
-- the @SockAddr@ of the switch, the @TransactionID@ of the echo reply,
-- and the data included in the echo reply.
switchEchoReplyE :: Event SwitchMessage -> Event (SockAddr, TransactionID, [Word8])
switchEchoReplyE = mapFilterE f
where f (PeerMessage sw (msgID, SCEchoReply bytes)) = Just (sw, msgID, bytes)
f _ = Nothing
-- | Outputs an event whenever a switch sends a packet in message.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, and the @PacketInfo@ message itself.
packetInE :: Event SwitchMessage -> Event (SockAddr, TransactionID, PacketInfo)
packetInE = mapFilterE f
where f (PeerMessage sw (msgID, PacketIn pktRecord)) = Just (sw, msgID, pktRecord)
f _ = Nothing
-- | Outputs an event whenever a switch sends a switch features message.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, and the @SwitchFeatures@ message itself.
switchFeaturesE :: Event SwitchMessage -> Event (SockAddr, TransactionID, SwitchFeatures)
switchFeaturesE = mapFilterE f
where f (PeerMessage sw (msgID, Features x)) = Just (sw, msgID, x)
f _ = Nothing
-- | Outputs an event whenever a switch sends a port update message.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, and the @PortStatus@ message itself.
portUpdateE :: Event SwitchMessage -> Event (SockAddr, TransactionID, PortStatus)
portUpdateE = mapFilterE f
where f (PeerMessage sw (msgID, M.PortStatus x)) = Just (sw, msgID, x)
f _ = Nothing
-- | Outputs an event whenever a switch sends an error message.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, and the @SwitchError@ message itself.
switchErrorE :: Event SwitchMessage -> Event (SockAddr, TransactionID, SwitchError)
switchErrorE = mapFilterE f
where f (PeerMessage sw (msgID, Error x)) = Just (sw, msgID, x)
f _ = Nothing
-- | Outputs an event whenever a switch sends an flow removed message.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, and the @FlowRemoved@ message itself.
flowRemovalE :: Event SwitchMessage -> Event (SockAddr, TransactionID, FlowRemoved)
flowRemovalE = mapFilterE f
where f (PeerMessage sw (msgID, M.FlowRemoved x)) = Just (sw, msgID, x)
f _ = Nothing
#if OPENFLOW_VERSION==152 || OPENFLOW_VERSION==1
-- | Outputs an event whenever a switch sends a barrier reply.
-- The event carries the @SockAddr@ of the switch and the @TransactionID@ of the
-- message.
barrierReplyE :: Event SwitchMessage -> Event (SockAddr, TransactionID)
barrierReplyE = mapFilterE f
where f (PeerMessage sw (msgID, BarrierReply)) = Just (sw, msgID)
f _ = Nothing
#endif
-- | Outputs an event whenever a switch sends statistics reply.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, and the @StatsReply@ message itself.
statsReplyE ::Event SwitchMessage -> Event (SockAddr, TransactionID, StatsReply)
statsReplyE = mapFilterE f
where f (PeerMessage sw (msgID, StatsReply reply)) = Just (sw, msgID, reply)
f _ = Nothing
-- | Outputs an event whenever a switch sends port statistics reply.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, a flag indicating whether more statistics for this reply will follow
-- in a separate message and the port statistics included in this message itself.
portStatsReplyE :: Event SwitchMessage -> Event (SockAddr, TransactionID, MoreToFollowFlag, [(PortID, PortStats)])
portStatsReplyE = statsReplyE >>> arr (mapFilterE f)
where f (addr, xid, PortStatsReply moreComing ports) = Just (addr, xid, moreComing, ports)
f _ = Nothing
-- | Outputs an event whenever a switch sends flow statistics reply.
-- The event carries the @SockAddr@ of the switch, the @TransactionID@ of the
-- message, a flag indicating whether more statistics for this reply will follow
-- in a separate message and the flow statistics included in this message itself.
flowStatsReplyE :: Event SwitchMessage -> Event (SockAddr, TransactionID, MoreToFollowFlag, [FlowStats])
flowStatsReplyE = statsReplyE >>> arr (mapFilterE f)
where f (addr, xid, FlowStatsReply moreComing stats) = Just (addr, xid, moreComing, stats)
f _ = Nothing
-- | Remove packet in messages not satisfying the given predicate.
filterPacketIns :: (PacketInfo -> Bool) -> Event SwitchMessage -> Event SwitchMessage
filterPacketIns p = mapFilterE p'
where p' m = case m of
PeerMessage switch (xid, PacketIn pktIn) -> if p pktIn then Just m else Nothing
_ -> Just m
-----------------------------------------------------
-- Switch output commands
-----------------------------------------------------
-- An implementation of HasSwitchCommands
type SwitchOutput = [(SockAddr, (TransactionID, CSMessage))]
switchCommand :: SockAddr -> TransactionID -> CSMessage -> SwitchOutput
switchCommand addr xid msg = [(addr, (xid, msg))]
-- | Another name for the unit element of a Monoid, specialized to the HasSwitchCommands class.
noOp :: Monoid o => o
noOp = mempty
-- | Infix binary operator for mappend.
(<+>) :: Monoid o => o -> o -> o
(<+>) = mappend
-- | Monoid instance for a Monoid embedded inside an Event.
instance Monoid a => Monoid (Event a) where
mempty = NoEvent
mappend = mergeBy mappend
-------------------------------------
-- | Runs an OpenFlow server at the specified port number, and return the driver IO actions.
switchInterfaceDriver :: ServerPortNumber -> IO (IO SwitchMessage, SwitchOutput -> IO ())
switchInterfaceDriver portNumber =
do process <- openFlowServer portNumber
let actuator csMsgs = writeAll process csMsgs
return (readP process, actuator)
-- | @openFlowController@ implements the basic aspects of the OpenFlow
-- protocol, such as the initial version negotiation and responding to
-- echo requests.
openFlowController :: SF (Event SwitchMessage) (Event SwitchOutput)
openFlowController =
proc i -> do
o1E <- greeter -< i
o2E <- echoHandler -< i
returnA -< mergeBy (<+>) o1E o2E
echoHandler :: SF (Event SwitchMessage) (Event SwitchOutput)
echoHandler = arr (fmap (\(addr, xid, bytes) -> switchCommand addr xid (CSEchoReply bytes)) . switchEchoRequestE)
greeter :: SF (Event SwitchMessage) (Event SwitchOutput)
greeter =
proc i -> do
returnA -< liftE (\(addr, msgID) -> switchCommand addr msgID CSHello) (switchHelloE i)