在管道上设计图书馆

时间:2014-06-25 05:27:18

标签: haskell conduit

现在我正在开发一种以太网数据包处理库。 基本思想是数据包有两种不同的来源: 网络接口和pcap转储文件。数据包应该分组 通过流,应该过滤流,UDP流应该由一个处理 方式,TCP通过另一种方式,等我开发了没有管道的版本 但我发现现在有太多重复的代码和什么时候 我试图抽象,然后发明类似于管道的东西。 所以我试图切换到导管然后卡住了。

所以图片是这样的:

                                   [UDP processing]
[pcap source]   |                 /                \
                |---[flow map]-->*                  *->[dump to many files]
                |                 \                /  
[iface source]  |                  [TCP processing]

第一个问题是流程图。它应该积累 当流量和流量中的数据包数量超过时 一些阈值 - 将其传递给处理。

第二个问题是我想要有所不同 用于UDP和TCP处理的管道,所以管道应该 以某种方式分裂。

所有这些东西应该是另一个问题 多线程,所以生产者和消费者应该 在不同的线程中。

那么这张照片中的内容应该是什么呢?

来源是消息来源,很清楚。但应该是什么 流程图?水槽,进一步产生源 处理?许多流量巨大,因此积累 在进一步处理之前必须在内存中的所有数据包 要避免。

有什么想法吗?再说一遍,如果没有这一切,如何做到这一点非常清楚 管道,所以问题是如何正确设计它。

UPD。

  data FlowFrame = FlowFrame { flowKey   :: !F.FlowKey
                             , flowFrame :: [Packet]
                             }

  data FlowState

  flowFrames :: MonadIO m => Conduit Packet m FlowFrame
  flowFrames = awaitForever $ \p -> do
    let (Right (l3, _)) = runGet F.readL3Headers (pktData p)
    let fk = F.flowKey l3
    yield (FlowFrame fk [p])

  sinkPrintFlow :: MonadIO m => Consumer FlowFrame m ()
  sinkPrintFlow = awaitForever $ liftIO.putStrLn.show.pPrint.flowKey

  isA :: F.Protocol -> FlowFrame -> Bool
  isA p frame =
    case ((flowKey frame)) of
      F.FlowKey p _ _ -> True
      _               -> False

  processUDP :: MonadIO m => Conduit FlowFrame m FlowFrame
  processUDP = CL.filter (isA F.PROTO_UDP)

  processTCP :: MonadIO m => Conduit FlowFrame m FlowFrame
  processTCP = CL.filter (isA F.PROTO_TCP)

  main = do 
    (file:_) <- getArgs
    input <- openOffline file
    sourcePcap input
      $$ flowFrames =$= void (sequenceConduits [processUDP, processTCP])
      $= sinkPrintFlow
    putStrLn "done"

2 个答案:

答案 0 :(得分:3)

如果使用pipes,则可以使用Pipes.Extras中的(+++)组合子并排运行两个管道。它有这种类型:

(+++)
    :: Monad m
    => Pipe a c m r
    -> Pipe b d m r
    -> Pipe (Either a b) (Either c d) m r

然后你的程序将成为:

producer >-> (udpPipe +++ tcpPipe) >-> consumer

每当您希望生产者将值转发到udpPipe时,您将值包装在Left中,并且每次要将值转发到tcpPipe时,您都将Right中的值。然后,下游consumer可以在其输入上进行模式匹配,以告知它来自哪个PipeLeft udpPipe值来自RighttcpPipe来自(+++)

编辑:请注意,这不需要并发。 {{1}}接受两个单线程管道并返回一个结合其逻辑的新单线程管道。

答案 1 :(得分:2)

您提到了一些不同的概念。让我依次回答:

  • 为了将两个不同的来源合并为一个来源,有多种选择。 ZipSource是一种常见的抽象,但可能并不是您正在寻找的东西。由于您可能希望同时执行此操作,因此我建议将每个数据源提供到共享通道(例如,TChan),然后从通道中读取一个Source。有关详细信息,请参阅stm-conduit
  • 要从单个Sink中读取两个不同的Source,您可以使用ZipSink。在您的情况下,这可能是正确的答案。在将源与相关的接收器融合之前,您可以将源过滤为仅TCP和UDP数据。
  • 通常,管道的所有组件都在一个线程中运行(这就是我们使用协同程序的原因)。为了能够在单独的线程中运行SourceSink,您可以使用Data.Conduit.Async(也来自stm-conduit)。

在像您这样的情况下,更明确地了解您在表面下使用并发原语,并让每个数据源或接收器直接与TChan对话。但这会涉及一些更复杂的设计问题,我真的无法就当前的信息给出任何明确的答案。

(顺便说一下,好的流程图。)