fix: extract Aenebris.Net.IP to dedupe SockAddr rendering (Finding 10)
- New module src/Aenebris/Net/IP.hs exposes sockAddrToIPBytes :: SockAddr -> ByteString. Single canonical implementation for IPv4 dotted-decimal, IPv6 colon-hex (8 groups), and unix-socket "unix:<path>" rendering. - Aenebris.RateLimit.clientIPKey reduces to `sockAddrToIPBytes . remoteHost`. Removes the local v6Bytes, the hostAddressToTuple/hostAddress6ToTuple/printf/showHex/ intercalate imports, and the duplicated implementation. - Aenebris.DDoS.ConnLimit drops its private copy of the same function. ipBytesFromSockAddr is kept as a thin alias for test backward-compatibility (= sockAddrToIPBytes), so existing callers and the connLimitSpec test continue to compile without rename churn. - aenebris.cabal: expose Aenebris.Net.IP in the library stanza (now 30 modules total). - test/Spec.hs: new netIpSpec asserts IPv4 dotted decimal, loopback, unix-socket prefix, and IPv6 colon-separated rendering (8 groups → 7 colons). Wires netIpSpec into main right after geoSpec. 362 examples passing, 0 failures, 0 GHC warnings.
This commit is contained in:
parent
d9dd59db2a
commit
15f795f10c
|
|
@ -38,6 +38,7 @@ library
|
||||||
, Aenebris.WAF.Engine
|
, Aenebris.WAF.Engine
|
||||||
, Aenebris.Honeypot
|
, Aenebris.Honeypot
|
||||||
, Aenebris.Geo
|
, Aenebris.Geo
|
||||||
|
, Aenebris.Net.IP
|
||||||
, Aenebris.ML.Features
|
, Aenebris.ML.Features
|
||||||
, Aenebris.ML.Model
|
, Aenebris.ML.Model
|
||||||
, Aenebris.ML.Loader
|
, Aenebris.ML.Loader
|
||||||
|
|
|
||||||
|
|
@ -28,19 +28,11 @@ import Control.Concurrent.STM
|
||||||
, readTVar
|
, readTVar
|
||||||
, writeTVar
|
, writeTVar
|
||||||
)
|
)
|
||||||
|
import Aenebris.Net.IP (sockAddrToIPBytes)
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString.Char8 as BS8
|
|
||||||
import Data.List (intercalate)
|
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
import Network.Socket
|
import Network.Socket (SockAddr)
|
||||||
( HostAddress6
|
|
||||||
, SockAddr(..)
|
|
||||||
, hostAddress6ToTuple
|
|
||||||
, hostAddressToTuple
|
|
||||||
)
|
|
||||||
import Numeric (showHex)
|
|
||||||
import Text.Printf (printf)
|
|
||||||
|
|
||||||
defaultPerIPLimit :: Int
|
defaultPerIPLimit :: Int
|
||||||
defaultPerIPLimit = 16
|
defaultPerIPLimit = 16
|
||||||
|
|
@ -87,19 +79,10 @@ currentCount ConnLimiter{..} ip = do
|
||||||
pure (Map.findWithDefault 0 ip m)
|
pure (Map.findWithDefault 0 ip m)
|
||||||
|
|
||||||
connLimitOnOpen :: ConnLimiter -> SockAddr -> IO Bool
|
connLimitOnOpen :: ConnLimiter -> SockAddr -> IO Bool
|
||||||
connLimitOnOpen cl sa = atomically (tryAcquire cl (ipBytesFromSockAddr sa))
|
connLimitOnOpen cl sa = atomically (tryAcquire cl (sockAddrToIPBytes sa))
|
||||||
|
|
||||||
connLimitOnClose :: ConnLimiter -> SockAddr -> IO ()
|
connLimitOnClose :: ConnLimiter -> SockAddr -> IO ()
|
||||||
connLimitOnClose cl sa = atomically (release cl (ipBytesFromSockAddr sa))
|
connLimitOnClose cl sa = atomically (release cl (sockAddrToIPBytes sa))
|
||||||
|
|
||||||
ipBytesFromSockAddr :: SockAddr -> ByteString
|
ipBytesFromSockAddr :: SockAddr -> ByteString
|
||||||
ipBytesFromSockAddr (SockAddrInet _ ha) =
|
ipBytesFromSockAddr = sockAddrToIPBytes
|
||||||
let (a, b, c, d) = hostAddressToTuple ha
|
|
||||||
in BS8.pack (printf "%d.%d.%d.%d" a b c d)
|
|
||||||
ipBytesFromSockAddr (SockAddrInet6 _ _ ha6 _) = v6Bytes ha6
|
|
||||||
ipBytesFromSockAddr (SockAddrUnix p) = BS8.pack ("unix:" <> p)
|
|
||||||
|
|
||||||
v6Bytes :: HostAddress6 -> ByteString
|
|
||||||
v6Bytes ha =
|
|
||||||
let (a, b, c, d, e, f, g, h) = hostAddress6ToTuple ha
|
|
||||||
in BS8.pack (intercalate ":" (map (`showHex` "") [a, b, c, d, e, f, g, h]))
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{-
|
||||||
|
©AngelaMos | 2026
|
||||||
|
IP.hs
|
||||||
|
-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Aenebris.Net.IP
|
||||||
|
( sockAddrToIPBytes
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import qualified Data.ByteString.Char8 as BS8
|
||||||
|
import Data.List (intercalate)
|
||||||
|
import Network.Socket
|
||||||
|
( HostAddress6
|
||||||
|
, SockAddr(..)
|
||||||
|
, hostAddress6ToTuple
|
||||||
|
, hostAddressToTuple
|
||||||
|
)
|
||||||
|
import Numeric (showHex)
|
||||||
|
import Text.Printf (printf)
|
||||||
|
|
||||||
|
ipv4Format :: String
|
||||||
|
ipv4Format = "%d.%d.%d.%d"
|
||||||
|
|
||||||
|
ipv6Separator :: String
|
||||||
|
ipv6Separator = ":"
|
||||||
|
|
||||||
|
unixSocketPrefix :: String
|
||||||
|
unixSocketPrefix = "unix:"
|
||||||
|
|
||||||
|
sockAddrToIPBytes :: SockAddr -> ByteString
|
||||||
|
sockAddrToIPBytes (SockAddrInet _ ha) =
|
||||||
|
let (a, b, c, d) = hostAddressToTuple ha
|
||||||
|
in BS8.pack (printf ipv4Format a b c d)
|
||||||
|
sockAddrToIPBytes (SockAddrInet6 _ _ ha6 _) = renderIPv6 ha6
|
||||||
|
sockAddrToIPBytes (SockAddrUnix p) = BS8.pack (unixSocketPrefix <> p)
|
||||||
|
|
||||||
|
renderIPv6 :: HostAddress6 -> ByteString
|
||||||
|
renderIPv6 ha =
|
||||||
|
let (a, b, c, d, e, f, g, h) = hostAddress6ToTuple ha
|
||||||
|
parts = [a, b, c, d, e, f, g, h]
|
||||||
|
in BS8.pack (intercalate ipv6Separator (map (`showHex` "") parts))
|
||||||
|
|
@ -29,9 +29,9 @@ import Control.Concurrent.STM
|
||||||
, readTVar
|
, readTVar
|
||||||
, writeTVar
|
, writeTVar
|
||||||
)
|
)
|
||||||
|
import Aenebris.Net.IP (sockAddrToIPBytes)
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString.Char8 as BS8
|
import qualified Data.ByteString.Char8 as BS8
|
||||||
import Data.List (intercalate)
|
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
|
@ -39,12 +39,6 @@ import qualified Data.Text as T
|
||||||
import qualified Data.Text.Read as TR
|
import qualified Data.Text.Read as TR
|
||||||
import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
|
import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
|
||||||
import Network.HTTP.Types (status429)
|
import Network.HTTP.Types (status429)
|
||||||
import Network.Socket
|
|
||||||
( HostAddress6
|
|
||||||
, SockAddr(..)
|
|
||||||
, hostAddress6ToTuple
|
|
||||||
, hostAddressToTuple
|
|
||||||
)
|
|
||||||
import Network.Wai
|
import Network.Wai
|
||||||
( Middleware
|
( Middleware
|
||||||
, Request
|
, Request
|
||||||
|
|
@ -52,8 +46,6 @@ import Network.Wai
|
||||||
, remoteHost
|
, remoteHost
|
||||||
, responseLBS
|
, responseLBS
|
||||||
)
|
)
|
||||||
import Numeric (showHex)
|
|
||||||
import Text.Printf (printf)
|
|
||||||
|
|
||||||
secondsPerMinute :: Double
|
secondsPerMinute :: Double
|
||||||
secondsPerMinute = 60
|
secondsPerMinute = 60
|
||||||
|
|
@ -165,18 +157,7 @@ rateLimitMiddleware rl app req respond = do
|
||||||
intBS n = BS8.pack (show n)
|
intBS n = BS8.pack (show n)
|
||||||
|
|
||||||
clientIPKey :: Request -> ByteString
|
clientIPKey :: Request -> ByteString
|
||||||
clientIPKey req = case remoteHost req of
|
clientIPKey = sockAddrToIPBytes . remoteHost
|
||||||
SockAddrInet _ ha ->
|
|
||||||
let (a, b, c, d) = hostAddressToTuple ha
|
|
||||||
in BS8.pack (printf "%d.%d.%d.%d" a b c d)
|
|
||||||
SockAddrInet6 _ _ ha6 _ -> v6Bytes ha6
|
|
||||||
SockAddrUnix p -> BS8.pack ("unix:" <> p)
|
|
||||||
where
|
|
||||||
v6Bytes :: HostAddress6 -> ByteString
|
|
||||||
v6Bytes ha =
|
|
||||||
let (a, b, c, d, e, f, g, h) = hostAddress6ToTuple ha
|
|
||||||
parts = [a, b, c, d, e, f, g, h]
|
|
||||||
in BS8.pack (intercalate ":" (map (`showHex` "") parts))
|
|
||||||
|
|
||||||
pathClassKey :: Request -> ByteString
|
pathClassKey :: Request -> ByteString
|
||||||
pathClassKey req =
|
pathClassKey req =
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,7 @@ import Aenebris.ML.Loader
|
||||||
( ParseError(..)
|
( ParseError(..)
|
||||||
, parseEnsemble
|
, parseEnsemble
|
||||||
)
|
)
|
||||||
|
import Aenebris.Net.IP (sockAddrToIPBytes)
|
||||||
import Aenebris.Middleware.Redirect (httpsRedirect, httpsRedirectWithPort)
|
import Aenebris.Middleware.Redirect (httpsRedirect, httpsRedirectWithPort)
|
||||||
import Aenebris.Middleware.Security
|
import Aenebris.Middleware.Security
|
||||||
( addSecurityHeaders
|
( addSecurityHeaders
|
||||||
|
|
@ -380,6 +381,7 @@ main = hspec $ do
|
||||||
wafSpec
|
wafSpec
|
||||||
honeypotSpec
|
honeypotSpec
|
||||||
geoSpec
|
geoSpec
|
||||||
|
netIpSpec
|
||||||
mlFeaturesSpec
|
mlFeaturesSpec
|
||||||
mlModelSpec
|
mlModelSpec
|
||||||
mlLoaderSpec
|
mlLoaderSpec
|
||||||
|
|
@ -1883,6 +1885,27 @@ mlModelSpec = describe "ML.Model" $ do
|
||||||
validateTree 20 bad `shouldSatisfy`
|
validateTree 20 bad `shouldSatisfy`
|
||||||
(\r -> case r of { Left _ -> True; Right _ -> False })
|
(\r -> case r of { Left _ -> True; Right _ -> False })
|
||||||
|
|
||||||
|
netIpSpec :: Spec
|
||||||
|
netIpSpec = describe "Net.IP" $ do
|
||||||
|
it "renders ipv4 sockaddr in dotted decimal" $
|
||||||
|
sockAddrToIPBytes (ipv4Addr (10, 0, 0, 1) 1234) `shouldBe` "10.0.0.1"
|
||||||
|
|
||||||
|
it "renders ipv4 loopback" $
|
||||||
|
sockAddrToIPBytes (ipv4Addr (127, 0, 0, 1) 8080) `shouldBe` "127.0.0.1"
|
||||||
|
|
||||||
|
it "renders unix sockaddr with prefix" $
|
||||||
|
sockAddrToIPBytes (SockAddrUnix "/tmp/sock") `shouldBe` "unix:/tmp/sock"
|
||||||
|
|
||||||
|
it "renders ipv6 sockaddr separated by colons (eight 16-bit groups)" $ do
|
||||||
|
let addr = SockAddrInet6
|
||||||
|
0
|
||||||
|
0
|
||||||
|
(tupleToHostAddress6 (0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))
|
||||||
|
0
|
||||||
|
result = sockAddrToIPBytes addr
|
||||||
|
BS.length result `shouldSatisfy` (> 0)
|
||||||
|
BC.count ':' result `shouldBe` 7
|
||||||
|
|
||||||
mlLoaderModel :: T.Text
|
mlLoaderModel :: T.Text
|
||||||
mlLoaderModel = T.unlines
|
mlLoaderModel = T.unlines
|
||||||
[ "tree"
|
[ "tree"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue