openflow-0.3.0: src/Network/Data/OpenFlow/MatchBuilder.hs
{-# LANGUAGE GADTs #-}
module Network.Data.OpenFlow.MatchBuilder
(
MatchBuilder
, Error
, Attribute
, match
, matchOrError
, (.&&.)
, (.==.)
, true
, in_port
, vlan_id
, vlan_priority
, eth_src
, eth_dst
, eth_type
, arp
, arp_ip_src
, arp_ip_dst
, ip
, ip_dst
, ip_src
, ip_prot
, ip_dscp
, icmp
, tcp
, tcp_src
, tcp_dst
, udp
, udp_src
, udp_dst
) where
import Network.Data.Ethernet.EthernetAddress
import Network.Data.Ethernet.EthernetFrame
import Network.Data.IPv4.IPAddress
import Network.Data.IPv4.IPPacket
import Network.Data.OpenFlow.Match
import Network.Data.OpenFlow.Port
import Data.Bits
import Data.Monoid
import Text.Printf (printf)
{-
data Match = Match !(Maybe PortID) !MatchHeader !MatchBody
data MatchHeader = MatchHeader { srcEthAddress, dstEthAddress :: !(Maybe EthernetAddress),
vLANID :: !(Maybe VLANID),
vLANPriority :: !(Maybe VLANPriority),
ethFrameType :: !(Maybe EthernetTypeCode) }
data MatchBody = MatchBody { ipTypeOfService :: !(Maybe IP.IPTypeOfService),
matchIPProtocol :: !(Maybe IP.IPProtocol),
srcIPAddress, dstIPAddress :: !IPAddressPrefix,
srcTransportPort, dstTransportPort :: !(Maybe IP.TransportPort)
}
-}
updSrcEthAddress :: MatchHeader -> Maybe EthernetAddress -> MatchHeader
updSrcEthAddress h ma = h { srcEthAddress = ma }
updDstEthAddress :: MatchHeader -> Maybe EthernetAddress -> MatchHeader
updDstEthAddress h ma = h { dstEthAddress = ma }
emptyMatch :: Match
emptyMatch = Match Nothing matchAnyHeader matchAnyBody
true :: MatchBuilder
true = liftBuilder Right
in_port_ :: PortID -> MatchBuilder
in_port_ pid = liftBuilder $ \m@(Match mInPort hdr bdy) ->
case mInPort of
Nothing -> Right (Match (Just pid) hdr bdy)
Just pid' -> if pid==pid' then Right m else Left "conflicing in_port ids"
eth_addr_set :: EthernetAddress
-> String
-> (MatchHeader -> Maybe EthernetAddress -> MatchHeader)
-> (MatchHeader -> Maybe EthernetAddress)
-> MatchBuilder
eth_addr_set addr name set get =
liftBuilder $ \m@(Match mInPort hdr bdy) ->
case get hdr of
Nothing -> Right (Match mInPort (set hdr (Just addr)) bdy)
Just addr' -> if addr==addr' then Right m else Left (printf "conflicing %s addresses" name)
eth_src_ :: EthernetAddress -> MatchBuilder
eth_src_ addr = eth_addr_set addr "eth_src" updSrcEthAddress srcEthAddress
eth_dst_ :: EthernetAddress -> MatchBuilder
eth_dst_ addr = eth_addr_set addr "eth_dst" updDstEthAddress dstEthAddress
updateSrcIPAddress, updateDstIPAddress :: IPAddressPrefix -> MatchBody -> MatchBody
updateSrcIPAddress prefix body = body { srcIPAddress = prefix}
updateDstIPAddress prefix body = body { dstIPAddress = prefix}
vlan_id_ :: VLANID -> MatchBuilder
vlan_id_ vid = liftBuilder $ \m@(Match mInPort hdr bdy) ->
case vLANID hdr of
Nothing -> Right (Match mInPort (hdr { vLANID = Just vid }) bdy)
Just vid' | vid' == vid -> Right m
| otherwise -> Left $ printf "conflicting vlan ids. Was %d, expecting %d" vid' vid
vlan_priority_ :: VLANPriority -> MatchBuilder
vlan_priority_ pcp = liftBuilder $ \m@(Match mInPort hdr bdy) ->
case vLANPriority hdr of
Nothing -> Right (Match mInPort (hdr { vLANPriority = Just pcp }) bdy)
Just pcp' | pcp' == pcp -> Right m
| otherwise -> Left $ printf "conflicting vlan priorities. Was %d, expecting %d" pcp' pcp
eth_type_ :: EthernetTypeCode -> MatchBuilder
eth_type_ c = liftBuilder $ \m@(Match mInPort hdr bdy) ->
case ethFrameType hdr of
Nothing -> Right (Match mInPort (hdr { ethFrameType = Just c }) bdy)
Just typ | typ == c -> Right m
| otherwise -> Left $ printf "conflicting eth types. Was %d, expecting %d" typ c
arp :: MatchBuilder
arp = liftBuilder $ \(Match mInPort hdr bdy) ->
case ethFrameType hdr of
Nothing -> Right (Match mInPort (hdr { ethFrameType = Just ethTypeARP }) bdy)
Just typ | typ == ethTypeARP -> Right (Match mInPort hdr bdy)
| otherwise -> Left "ACK conflicting L3 packet types"
arp_ip_src_ :: IPAddressPrefix -> MatchBuilder
arp_ip_src_ prefix = liftBuilder $ \(Match mInPort hdr bdy) ->
let bdy' = bdy { srcIPAddress = prefix} in
case ethFrameType hdr of
Nothing -> Right (Match mInPort (hdr { ethFrameType = Just ethTypeARP }) bdy')
Just typ | typ == ethTypeARP -> Right (Match mInPort hdr bdy')
| otherwise -> Left "ACK conflicting L3 packet types"
arp_ip_dst_ :: IPAddressPrefix -> MatchBuilder
arp_ip_dst_ prefix = liftBuilder $ \(Match mInPort hdr bdy) ->
let bdy' = bdy { dstIPAddress = prefix} in
case ethFrameType hdr of
Nothing -> Right (Match mInPort (hdr { ethFrameType = Just ethTypeARP }) bdy')
Just typ | typ == ethTypeARP -> Right (Match mInPort hdr bdy')
| otherwise -> Left "ACK conflicting L3 packet types"
ip :: MatchBuilder
ip = liftBuilder $ \(Match mInPort hdr bdy) ->
case ethFrameType hdr of
Nothing -> Right (Match mInPort (hdr { ethFrameType = Just ethTypeIP }) bdy)
Just typ | typ == ethTypeIP -> Right (Match mInPort hdr bdy)
| otherwise -> Left "conflicting L3 packet types"
set_ip_field :: (MatchBody -> MatchBody) -> MatchBuilder
set_ip_field set = liftBuilder $ \(Match mInPort hdr bdy) ->
case ethFrameType hdr of
Nothing -> Right (Match mInPort (hdr { ethFrameType = Just ethTypeIP }) (set bdy))
Just typ | typ == ethTypeIP -> Right (Match mInPort hdr (set bdy))
| otherwise -> Left "conflicting L3 packet types"
ip_dst_ :: IPAddressPrefix -> MatchBuilder
ip_dst_ = set_ip_field . updateDstIPAddress
ip_src_ :: IPAddressPrefix -> MatchBuilder
ip_src_ = set_ip_field . updateSrcIPAddress
dscp_ :: DifferentiatedServicesCodePoint -> MatchBuilder
dscp_ x = set_ip_field (\bdy -> bdy { ipTypeOfService = Just (shiftL x 2) })
icmp :: MatchBuilder
icmp = set_ip_field (\bdy -> bdy { matchIPProtocol = Just ipTypeIcmp })
tcp :: MatchBuilder
tcp = set_ip_field (\bdy -> bdy { matchIPProtocol = Just ipTypeTcp })
udp :: MatchBuilder
udp = set_ip_field (\bdy -> bdy { matchIPProtocol = Just ipTypeUdp })
ip_prot_ :: IPProtocol -> MatchBuilder
ip_prot_ p = set_ip_field (\bdy -> bdy { matchIPProtocol = Just p })
tcp_src_ :: TCPPortNumber -> MatchBuilder
tcp_src_ = set_tcp_field . updateTransportSrc
tcp_dst_ :: TCPPortNumber -> MatchBuilder
tcp_dst_ = set_tcp_field . updateTransportDst
udp_src_ :: TCPPortNumber -> MatchBuilder
udp_src_ = set_udp_field . updateTransportSrc
udp_dst_ :: TCPPortNumber -> MatchBuilder
udp_dst_ = set_udp_field . updateTransportDst
updateTransportSrc :: TCPPortNumber -> MatchBody -> MatchBody
updateTransportSrc s bdy = bdy { srcTransportPort = Just s }
updateTransportDst :: TCPPortNumber -> MatchBody -> MatchBody
updateTransportDst s bdy = bdy { dstTransportPort = Just s }
set_tcp_field :: (MatchBody -> MatchBody) -> MatchBuilder
set_tcp_field = set_transport_field ipTypeTcp
set_udp_field :: (MatchBody -> MatchBody) -> MatchBuilder
set_udp_field = set_transport_field ipTypeUdp
set_transport_field :: IPProtocol -> (MatchBody -> MatchBody) -> MatchBuilder
set_transport_field prot' set = liftBuilder $ \(Match mInPort hdr bdy) ->
case ethFrameType hdr of
Nothing ->
let hdr' = hdr { ethFrameType = Just ethTypeIP }
bdy' = set (bdy { matchIPProtocol = Just prot' })
in Right $ Match mInPort hdr' bdy'
Just typ
| typ == ethTypeIP ->
case matchIPProtocol bdy of
Nothing -> let bdy' = set (bdy { matchIPProtocol = Just prot' })
in Right $ Match mInPort hdr bdy'
Just prot | prot == prot' -> Right $ Match mInPort hdr (set bdy)
| otherwise -> Left "conflicting transport layer types"
| otherwise -> Left "Cannot set transport field of non-IP frame"
newtype MatchBuilder = MatchBuilder (Either Error Match -> Either Error Match)
type Error = String
instance Monoid MatchBuilder where
mempty = MatchBuilder id
mappend (MatchBuilder f) (MatchBuilder g) = MatchBuilder (f.g)
(.&&.) :: MatchBuilder -> MatchBuilder -> MatchBuilder
(.&&.) = mappend
data Attribute a where
ETH_SRC :: Attribute EthernetAddress
ETH_DST :: Attribute EthernetAddress
ETH_TYP :: Attribute EthernetTypeCode
VLAN_ID :: Attribute VLANID
VLAN_PRIORITY :: Attribute VLANPriority
ARP_IP_SRC :: Attribute IPAddressPrefix
ARP_IP_DST :: Attribute IPAddressPrefix
IP_SRC :: Attribute IPAddressPrefix
IP_DST :: Attribute IPAddressPrefix
IP_PROT :: Attribute IPProtocol
IP_DSCP :: Attribute DifferentiatedServicesCodePoint
TCP_SRC :: Attribute TCPPortNumber
TCP_DST :: Attribute TCPPortNumber
UDP_SRC :: Attribute TCPPortNumber
UDP_DST :: Attribute TCPPortNumber
IN_PORT :: Attribute PortID
in_port :: Attribute PortID
in_port = IN_PORT
vlan_id :: Attribute VLANID
vlan_id = VLAN_ID
vlan_priority :: Attribute VLANPriority
vlan_priority = VLAN_PRIORITY
eth_src :: Attribute EthernetAddress
eth_src = ETH_SRC
eth_dst :: Attribute EthernetAddress
eth_dst = ETH_DST
eth_type :: Attribute EthernetTypeCode
eth_type = ETH_TYP
arp_ip_src :: Attribute IPAddressPrefix
arp_ip_src = ARP_IP_SRC
arp_ip_dst :: Attribute IPAddressPrefix
arp_ip_dst = ARP_IP_DST
ip_src :: Attribute IPAddressPrefix
ip_src = IP_SRC
ip_dst :: Attribute IPAddressPrefix
ip_dst = IP_DST
ip_prot :: Attribute IPProtocol
ip_prot = IP_PROT
ip_dscp :: Attribute DifferentiatedServicesCodePoint
ip_dscp = IP_DSCP
tcp_src :: Attribute TCPPortNumber
tcp_src = TCP_SRC
tcp_dst :: Attribute TCPPortNumber
tcp_dst = TCP_DST
udp_src :: Attribute TCPPortNumber
udp_src = UDP_SRC
udp_dst :: Attribute TCPPortNumber
udp_dst = UDP_DST
(.==.) :: Attribute a -> a -> MatchBuilder
(.==.) VLAN_ID vid = vlan_id_ vid
(.==.) VLAN_PRIORITY pcp = vlan_priority_ pcp
(.==.) IN_PORT p = in_port_ p
(.==.) ETH_DST addr = eth_dst_ addr
(.==.) ETH_SRC addr = eth_src_ addr
(.==.) ETH_TYP c = eth_type_ c
(.==.) ARP_IP_SRC addr = arp_ip_src_ addr
(.==.) ARP_IP_DST addr = arp_ip_dst_ addr
(.==.) IP_DST addr = ip_dst_ addr
(.==.) IP_SRC addr = ip_src_ addr
(.==.) IP_PROT p = ip_prot_ p
(.==.) IP_DSCP x = dscp_ x
(.==.) TCP_DST s = tcp_dst_ s
(.==.) TCP_SRC s = tcp_src_ s
(.==.) UDP_DST s = udp_dst_ s
(.==.) UDP_SRC s = udp_src_ s
infixr 4 .&&.
infix 6 .==.
liftBuilder :: (Match -> Either Error Match) -> MatchBuilder
liftBuilder f = MatchBuilder $ \x ->
case x of
Left e -> Left e
Right m -> f m
match :: MatchBuilder -> Match
match b =
case matchOrError b of
Left e -> error e
Right m -> m
matchOrError :: MatchBuilder -> Either Error Match
matchOrError (MatchBuilder f) = f (Right emptyMatch)