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:
CarterPerez-dev 2026-04-29 02:19:56 -04:00
parent d9dd59db2a
commit 15f795f10c
5 changed files with 74 additions and 43 deletions

View File

@ -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

View File

@ -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]))

View File

@ -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))

View File

@ -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 =

View File

@ -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"