HaskellでSNMP Trapを投げる
Haskellには、SNMP Trapを送信するライブラリがないですかね。
ということで、簡単なものだけ作ってみました。
目指したものとしては、
-
送信したいSNMP Trapを複数定義した設定ファイルを読み込む
-
SNMP Trapの定義ごとにSNMP Trapのパケットを組み立てる
-
SNMP Trapの定義ごとにスレッドを立てて、SNMP Trapを送信する(指定した間隔で投げ続ける)
と言った感じです。
+++
1. 送信したいSNMP Trapを複数定義した設定ファイルを読み込む
設定ファイルは iniファイル の形式で、作成します。
ライブラリは ConfigFile を使用していきます。
readConfig :: IO ConfigParserreadConfig = getCurrentDirectory >>= getDirectoryContents >>= return . head . filter ("config.ini" `isSuffixOf`) >>= readfile emptyCP >>= return . either (const emptyCP) idツールの実行ディレクトリと同じフォルダにある iniファイル を読み込んで、ConfigParserを返します。
今後、必要な変数などはこのConfigParserから取得していきます。
+++
2. SNMP Trapの定義ごとにSNMP Trapのパケットを組み立てる
SNMP TrapはASN1という規格でエンコードされます。
このエンコードには、asn1-encoding を使っていきます。
今回は、簡単に、ということで、v1のトラップで、bind変数もstringを1つだけ、、ということでやっていきます・・・(>_<;)
makeASN1TrapMsgs :: ConfigParser -> [SectionSpec] -> [B.ByteString]makeASN1TrapMsgs _ [] = []makeASN1TrapMsgs cp (s:ss) = (B.concat $ BL.toChunks $ encodeASN1 DER (makeASN1TrapData cp s)) : makeASN1TrapMsgs cp ss複数のSNMP Trapを定義するということで、iniファイルのセクションごとに、SNMP Trapを定義して、セクションごとにSNMP Trapのメッセージを組み立てていきます。
encodeASN1は遅延評価されますが、後の送信処理のところでは正格評価されるので、ここで変換しておきます。
makeASN1TrapData :: ConfigParser -> SectionSpec -> [ASN1]makeASN1TrapData cp sec | version == "1" = asn1Trap1Data cp sec | otherwise = [Null] where version = forceEither $ get cp sec "snmp_version"今回は、v1のみ対応ということで。。
設定値は、forceEitherで取っていきます。iniファイルの設定はちゃんと出来ているということで。。
asn1Trap1Data :: ConfigParser -> SectionSpec -> [ASN1]asn1Trap1Data cp sec = [ Start Sequence -- SNMP packet start , IntVal 0 -- SNMP version: version-1 , OctetString community -- SNMP community , Start (Container Context 4) -- SNMP trap pdu v1 start , OID enterpriseId -- Enterprise OID , Other Application 0 agentAddress -- Agent Address , IntVal genericTrap -- Generic trap , IntVal specificTrap -- Specific trap , Other Application 3 timeTicks -- Time ticks , Start Sequence -- Variable binding list start , Start Sequence -- 1st variable binding start , OID varbindOid -- Object name , OctetString varbindMsg -- Value , End Sequence -- 1st variable binding end , End Sequence -- Variable binding list end , End (Container Context 4) -- SNMP trap pdu v1 end , End Sequence -- SNMP Packaet end ] where community = C.pack $ forceEither $ get cp sec "snmp_community" enterpriseId = map (\s -> read s :: Integer) $ dropWhile (=="") $ splitOn "." $ forceEither $ get cp sec "enterprise_oid" agentAddress = B.pack $ map (\s -> read s :: Word8) $ splitOn "." $ forceEither $ get cp sec "agent_ip_address" genericTrap = read (forceEither $ get cp sec "generic_trap") :: Integer specificTrap = read (forceEither $ get cp sec "specific_trap") :: Integer timeTicks' = read (forceEither $ get cp sec "time_stamp") :: Integer timeTicks = case timeTicks' of 0 -> B.pack [0] _ -> B.dropWhile (==0) $ encode timeTicks' varbindOid = map (\s -> read s :: Integer) $ dropWhile (=="") $ splitOn "." $ forceEither $ get cp sec "varbind_oid" varbindMsg = C.pack $ forceEither $ get cp sec "varbind_msg"SNMP Trapのパケットを組み立てていきます。
SNMP Trapのパケットの構造は、RFCや、パケットキャプチャを参照いただくとして、その構造に合わせて、ASN1のリストを作っていきます。
ASN1は、<データの型>、<データの長さ>、<データ本体>の形式でエンコードされます。そのため、複数のデータをまとめる型の場合、その開始と終了を明示する必要があります。
各設定値は、ConfigParserからゴリゴリ読み込んでいきます。
+++
3. SNMP Trapの定義ごとにスレッドを立てて、SNMP Trapを送信する
SNMP Trapの送信は network を使っていきます。
また、SNMP Trapの定義ごとに別スレッドを立てていくのには、Control.Concurrent を使います。
sendTrap :: ConfigParser -> [B.ByteString] -> [ThreadId] -> IO [ThreadId]sendTrap _ [] ts = return tssendTrap cp (msg:msgs) ts = do thread <- forkIO $ sendTrapBy intval server msg sendTrap cp msgs (thread:ts) where intval = read (forceEither $ get cp "DEFAULT" "trap_send_interval") :: Int server = forceEither $ get cp "DEFAULT" "server_ip_address"Control.ConcurrentのforkIOを使ってSNMP Trapの定義ごとにスレッドを起動してきます。
sendTrapBy :: Int -> String -> B.ByteString -> IO ()sendTrapBy intval server trap = do sendTrapTo server trap threadDelay (intval * 1000) sendTrapBy intval server trapthreadDelayを使って、SNMP Trapを定期的な間隔で送信していきます。
sendTrapTo :: String -> B.ByteString -> IO ()sendTrapTo server trap = withSocketsDo $ do addrs <- getAddrInfo Nothing (Just server) (Just "snmptrap") let addr = head addrs sock <- socket AF_INET Datagram defaultProtocol bind sock (SockAddrInet aNY_PORT iNADDR_ANY) connect sock (addrAddress addr) sendAll sock trap close sock送信処理は、送信先サーバの情報を設定から取得し、UDPで接続するソケットを作成します。
送信元IPとポートは自動で割り当てにして、接続したら、組み立てたパケットを送信して、ソケットクローズです。
waitLoadTime <- loadTime cpwhen waitLoadTime $ mapM_ killThread sendTrapThreadsSNMP Trapの送信処理は、指定した時間経過したら終了するようにするため、
killThreadで停止していきます。
+++
SNMP Trapを送信するには、いろいろ決めないと行けないので、設定情報を作るのも結構面倒ですね。
ただ、監視の仕組みを導入するときは、性能などを確認するために、負荷ツールが必要になってくるので、どうやったら、楽になるかなぁと考えつつ、改良してみようかなと思います。
追記:
ソースはこちらにあります。