diff --git a/docs/devlog.md b/docs/devlog.md index 53ed0821..21beb6a3 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -1,3 +1,7 @@ +## 2023-10-22 + +тестируем шифрование на уровне протокола + ## 2023-10-21 тестируем substituter diff --git a/docs/proto/encryption.texi b/docs/proto/encryption.texi new file mode 100644 index 00000000..819837f6 --- /dev/null +++ b/docs/proto/encryption.texi @@ -0,0 +1,349 @@ +\input texinfo + +@documentencoding UTF-8 +@node Top +@top Дизайн шифрования протокола + +@chapter Предпосылки + +@section Борьба с DPI + +С высокой вероятностью, на текущем нашем уровне понимания +проблемы, DPI будут распознавать протокол всё равно. + +Соответственно, нет большой разницы, один - два или ноль +незашифрованных пакетов пройдёт. + +Само наличие шифрования --- уже признак для DPI, могут +блокировать просто на основании принадлежности неизвестному +протоколу. + +Размеры пакетов и характер обмена --- тоже признаки. + +Saltine, возможно, оставляет сигнатуры в зашифрованных пакетах +(проверить). + +Таким образом, борьба с DPI совершенно точно должна +осуществляться отдельным Messaging, может быть даже внешним. + +Нашего текущего уровня понимания проблемы просто-таки +недостаточно для эффективной борьбы без радикального усложнения +себе жизни. + +В дальнейшем, для протокола борьбы с DPI можно выделить даже +отдельные порты, где будут ожидаться другие протоколы. + +Скорее всего, нужно будет просто написать транспорт поверх +WS/WSS, а внутренние датаграммы шифровать как обычно. + +Таким образом, наверху будет TLS, а внизу --- наше шифрование +пакетов, при этом протокол будет принадлежать к "хорошо +известным протоколам". Можно даже палёные сертификаты +использовать, что бы успокоить службы (на верхнем уровне). + + +@section Различение шифрованных и нешифрованных пакетов + +Возможны следующие способы: + +@itemize @bullet +@item Номера портов +@item Сам протокол +@end itemize + +@subsection Номера портов + +Запускаем Messaging на отдельном порту, там воркер, +который занимается шифрованием и хэндшейком, +по мере расшифровки --- передает данные в указанный Messaging +(Proxy). + + +Плюсы: + +@itemize @bullet +@item Не нужно заглядывать в пакеты: будет быстрее. +@item Не нужны префиксы в протоколе +@item Не будет интерференции с остальными протоколами +@item Обратная совместимость +@end itemize + + +Минусы: + +@itemize @bullet +@item Доработка PEX +@item Доработка бутстрапа +@item Различение шифрованных и нешифрованных протоколов либо + хардкода портов +@item Устойчивые порты => легко блокировать +@end itemize + +@subsection Сам протокол + +Поскольку Messaing работает @strong{ДО}, мы можем в нём +распаковывать пакет, доставать сообщение и передавать в Peer уже +расшифрованное. + +У нас есть два варианта: + +Сообщение зашифрованное и незашифрованное. + +Допустим, сообщение зашифрованное --- тогда это просто пакет в +формате, который создает libsodium. + +Незашифрованное --- содержит префикс, например 0xdeadf1od и +дальше --- уже сам пакет. + +При приеме сообщения мы отправляем этот префикс в начале пакета. + +Если префикс есть --- то смотрим, что это за пакет. + +Допустим, это хэндшейк. Тогда производим согласование ключей, и +дальше шлём зашифрованные пакеты. + +Допустим, префикс будет только у пакетов хэндшейка, тогда +мы даже сохраним обратную совместимость --- неразобранные пакеты +будем отправлять по стеку дальше. + +Минус устойчивой сигнатуры в определенном месте --- легко +блокировать простейшим фильтром. + +Несмотря на то, что наши возможности бороться с DPI ограничены +слабым погружением в вопрос, настолько облегчать работу им мы не +хотим. + +Можно в префикс встроить некий байткод, выполнение которого +приведёт к вычислению публичного ключа пира, которым можно, +в частности, идентифицировать самого пира. + +Тогда это не будет создавать значительных проблем самому +пиру, но будет создавать проблемы при массовом анализе +протоколов. Можно еще и загадку встроить. + +Тогда сам пакет будет выглядеть более или менее как мусор, +для анализа придётся построить интерпретатор, а так же встроить +интерпретацию в DPI, успехов им в борьбе. + +Можно так же использовать какой-то вид вывода ключей из хорошо +известной, но динамически меняющейся информации. + +А можно использовать всё вместе, расширяя этот байткод по мере +необходимости. + +Всё это приведёт к большому усложнению и сторонних реализаций +протокола тоже, так что, для начала --- можно выбрать самый +простой способ распознать пакет хэндшейка. + +Но пожалуй, я настою на том, что бы это был какой-то +динамический способ, что бы не зависеть от статических +сигнатур. + +Вычисляться должно быстро, желательно +в один проход вперёд. + + +Плюсы: + +@itemize @bullet +@item Меньше кода +@item Не меняется PEX +@item Нет устойчивых номеров портов +@item Можно навесить сверху любого протокола, + например, RPC +@end itemize + +Минусы: + +@itemize @bullet +@item Потенциально медленнее +@end itemize + +@subsection Дизайн + +Отдельный Proxy на каждый Messaging, с общим KeyStore. + +@itemize @bullet +@item Пир сразу идентифицируется для всех протоколов +@item Можно надстроить над любым Messaing, в частности, над RPC, +который у нас пока без средств шифрования и аутентификации. +@end itemize + +@verbatim +keys <- newKeyStore +proxy1 <- newProxyEnc keys (newMessaingUDP ...) +proxy2 <- newProxyEnc keys (newMessaingTCP ...) +proxy3 <- newProxyMessaging proxy1 (Just proxy2) +peer <- newPeer ... (Fabriq proxy3) +@end verbatim + +Далее. Предусмотрим два режима: bypass и drop. + +@subsection Режим bypass + +Пробует провести handshake, если не удаётся --- +то оставляет эти попытки и просто пересылвает сообщения, +как есть на следующий уровень. + +Хэндшейк делает сам. <<Чужие>> пакеты при этом пропускает +и наверх, и вниз. + +Таким образом, остаётся обратная совместимость --- ведь +пакеты этой прокси просто будут дропнуты. + +Так же можно пакеты слать культурно, в виде AnyMessage +с каким-то несуществующим типом протокола, который +отсутствует в каких-либо обработчиках --- тогда их +будет видно в логах. Возможно, это излишнее. + +@subsection Режим drop + +Пробует провести handshake, если не удаётся --- +то не делает ничего, либо повторяет их заданное число раз, +а потом не делает ничего. + +Этот режим выбирают пиры, которые настаивают на защищенном +обмене. + +@subsection Сообщения и FSM + +Незашифрованное: + +1. HEY(PREFIX,PKs,PKe,SIGN(PKs, PKe)) + +@table @asis + +@item PKs: +ключ подписи пира + +@item PKe: +публичный ключ шифрования сессии + +@end table + +Зашифрованное: + +2. HEYOURSELF(PKe,BOX(PKe,Sid,SECRET)) + +@table @asis + +@item PKe: +публичный ключ шифрования сессии + +@item Sid: +Идентификатор ключа на нашей стороне, +должен быть в открытом виде в зашифрованном пакете + +@end table + +Пересылаем ключ шифрования, теперь пир может слать нам +зашифрованные сообщения. + +Просто пытаемся расшифровать их своим секретом. Неудача --- +просто дропаем или пропускаем, в зависимости от политики. + +Теперь, если пир продолжает слать незашифрованные +сообщения --- можем их или дропать, или продолжать +пропускать. + +После получения HEYOURSELF мы должны пиру послать +наш секрет ключ тоже, так что должны видимо, в свою +очередь ответить HEY. + + +@verbatim + + Peer1 Peer2 + ***** **** + | HEY | + |------------------>| + | HEYOURSELF | + |<------------------| Теперь Peer1 может слать Peer2 + | | зашифрованные сообщения + | | + | HEY | + |<------------------| Теперь Peer2 может слать Peer1 + | HEYOURSELF | зашифрованные сообщения + |------------------>| + | | + | | + +@end verbatim + +Заметим, что это хорошо ложится на систему обработки +подпротоколов в Peer: пары HEY/YOUSELF являются +независимыми, и, кажется, stateless, в том плане, +что нонсом тут явлется сам PKe. + +Но! Если это вынести на уровень Peer, то потеряется +возможность навесить аутентификацию/шифрование на любой +Messaging. + +Поэтому делаем на уровне Messaging. + +Грубо говоря: получили HEYOURSELF --- обновили ключ. + +Исключительная ситуация: + +Пир потерял наш SECRET и об этом не знает. + +Это может случиться только при рестарте, если мы не сохраняем +ключ. + +Но это не может случиться, так как, если у ProxyEnc нет ключа +--- то она посылает HEY. + + +Замечание: ProxyEnc является пассивной, она ничего не знает про +пиров, PEX и сама не инициирует общение, так как не знает, с кем +общаться. + +Поэтому, в режиме Bypass --- просто пропускает сообщения, как +есть, в обе стороны, не препятствуя. Как только удалось +согласовать ключ --- то есть, получить HEYOURSELF --- то +включаем шифрование. + +Если пинг прислал нам HEYOURSELF, а сам незашифрованные +сообщения шлёт -- ну дурак, чо. Надо проинформировать об этом +в лог, может забанить его. + +В режиме Drop -- ставит сообщения в очередь, пока не появился +ключ. + +Как только ключ появился --- шифрует и отправляет. + +Пиры авторизуются друг у друга при помощи протокола Ping. + +Никаких других ходить не должно до этого, это в принципе ошибка. + +Соответственно, пока пиры не авторизовались --- никакого обмена +нет. + +Даже если мы этот пинг дропнем --- пир нас пинганёт еще раз. + +@strong{Про Sid} Так как один и тот же пир может быть под разными +адресами одновременно, а PeerNonce и прочее --- на этом уровне протокола +недоступно --- будем генерировать уникальный ключ для каждого пира по +адресу, и в HEYOURSELF пересылать Sid ключа. Пир будет сохранять пару +(Sid, SECRET) --- и в ответе будет брать нужный ключ. + +Проблема, если мы пишем пиру на один адрес (каким нам ключом шифровать?), +а он отвечает нам с другого адреса. + +PeerNonce недоступен, что делать? + +Кейс: + +Peer1: Peer2: + tcp:1.1.1.1:8957 -> tcp:1.1.10.5:8001 + <- tcp:1.1.10.5:62511 + + +В HEYO(SELF) посылаем ему ID ключа, он где-то (где?) запоминает, +что нам надо слать c таким ID ключа. + + + +@bye + + diff --git a/hbs2-core/hbs2-core.cabal b/hbs2-core/hbs2-core.cabal index 85b49bcb..d669e4aa 100644 --- a/hbs2-core/hbs2-core.cabal +++ b/hbs2-core/hbs2-core.cabal @@ -101,6 +101,8 @@ library , HBS2.Net.Messaging.TCP , HBS2.Net.Messaging.Unix , HBS2.Net.Messaging.Stream + , HBS2.Net.Messaging.Encrypted.RandomPrefix + , HBS2.Net.Messaging.Encrypted.ByPass , HBS2.Net.PeerLocator , HBS2.Net.PeerLocator.Static , HBS2.Net.Proto diff --git a/hbs2-core/lib/HBS2/Actors/Peer.hs b/hbs2-core/lib/HBS2/Actors/Peer.hs index 258d1b20..4efde844 100644 --- a/hbs2-core/lib/HBS2/Actors/Peer.hs +++ b/hbs2-core/lib/HBS2/Actors/Peer.hs @@ -47,20 +47,6 @@ import Control.Monad.IO.Unlift import Codec.Serialise (serialise, deserialiseOrFail) -data AnyMessage enc e = AnyMessage !Integer !(Encoded e) - deriving stock (Generic) - - -class ( Messaging (Fabriq e) e (AnyMessage (Encoded e) e) - , Eq (Encoded e) - , Hashable (Encoded e) - ) => PeerMessaging e - -instance ( Messaging (Fabriq e) e (AnyMessage (Encoded e) e) - , Eq (Encoded e) - , Hashable (Encoded e) - ) - => PeerMessaging e class ( Eq (SessionKey e a) , Hashable (SessionKey e a) @@ -379,6 +365,7 @@ newPeerEnv :: forall e m . ( MonadIO m , Ord (Peer e) , Pretty (Peer e) , HasNonces () m + , PeerMessaging e , Asymm (Encryption e) , Hashable (PubKey 'Sign (Encryption e)) , Hashable PeerNonce @@ -457,7 +444,7 @@ runProto hh = do case Map.lookup n disp of Nothing -> do - err $ "PROTO not found" <+> pretty n <+> pretty (fmap fst resp) + -- err $ "PROTO not found" <+> pretty n <+> pretty (fmap fst resp) pure () -- FIXME: error counting! and statistics counting feature Just (AnyProtocol { protoDecode = decoder diff --git a/hbs2-core/lib/HBS2/Actors/Peer/Types.hs b/hbs2-core/lib/HBS2/Actors/Peer/Types.hs index 6aaacefd..6a0aac5e 100644 --- a/hbs2-core/lib/HBS2/Actors/Peer/Types.hs +++ b/hbs2-core/lib/HBS2/Actors/Peer/Types.hs @@ -36,7 +36,15 @@ class Monad m => HasOwnPeer e m where data Fabriq e = forall bus . (Messaging bus e (Encoded e)) => Fabriq bus + class HasFabriq e m where getFabriq :: m (Fabriq e) +data AnyMessage enc e = AnyMessage !Integer !(Encoded e) + deriving stock (Generic) + +type PeerMessaging e = ( Messaging (Fabriq e) e (AnyMessage (Encoded e) e) + , Eq (Encoded e) + , Hashable (Encoded e) + ) diff --git a/hbs2-core/lib/HBS2/Net/Auth/GroupKeySymm.hs b/hbs2-core/lib/HBS2/Net/Auth/GroupKeySymm.hs index 13473440..90fd2a49 100644 --- a/hbs2-core/lib/HBS2/Net/Auth/GroupKeySymm.hs +++ b/hbs2-core/lib/HBS2/Net/Auth/GroupKeySymm.hs @@ -162,9 +162,6 @@ lookupGroupKey sk pk gk = runIdentity $ runMaybeT do -- error $ "DECRYPTED SHIT!" MaybeT $ pure $ deserialiseOrFail (LBS.fromStrict gkBs) & either (const Nothing) Just --- FIXME: move-to-appropriate-place -class NonceFrom nonce a where - nonceFrom :: a -> nonce typicalNonceLength :: Integral a => a typicalNonceLength = unsafePerformIO SK.newNonce & Saltine.encode & B8.length & fromIntegral diff --git a/hbs2-core/lib/HBS2/Net/Messaging.hs b/hbs2-core/lib/HBS2/Net/Messaging.hs index b093b3d6..dc507e2d 100644 --- a/hbs2-core/lib/HBS2/Net/Messaging.hs +++ b/hbs2-core/lib/HBS2/Net/Messaging.hs @@ -1,7 +1,9 @@ -{-# Language FunctionalDependencies #-} {-# Language UndecidableInstances #-} {-# Language AllowAmbiguousTypes #-} -module HBS2.Net.Messaging where +module HBS2.Net.Messaging + ( module HBS2.Net.Messaging + , module HBS2.Net.Proto + ) where import HBS2.Net.Proto @@ -20,3 +22,4 @@ class HasPeer proto => Messaging bus proto msg where sendTo :: MonadIO m => bus -> To proto -> From proto -> msg -> m () receive :: MonadIO m => bus -> To proto -> m [(From proto, msg)] + diff --git a/hbs2-core/lib/HBS2/Net/Messaging/Encrypted/ByPass.hs b/hbs2-core/lib/HBS2/Net/Messaging/Encrypted/ByPass.hs new file mode 100644 index 00000000..67aff5dc --- /dev/null +++ b/hbs2-core/lib/HBS2/Net/Messaging/Encrypted/ByPass.hs @@ -0,0 +1,517 @@ +{-# Language UndecidableInstances #-} +{-# Language RecordWildCards #-} +module HBS2.Net.Messaging.Encrypted.ByPass + ( ForByPass + , ByPass + , ByPassOpts(..) + , ByPassStat(..) + , byPassDef + , newByPassMessaging + , cleanupByPassMessaging + , getStat + ) where + +import HBS2.Prelude +import HBS2.Hash +import HBS2.Clock hiding (sec) +import HBS2.Net.Messaging +import HBS2.Data.Types.SignedBox +import HBS2.Net.Proto.Definition() +import HBS2.Net.Auth.Credentials() + +import HBS2.Net.Messaging.Encrypted.RandomPrefix + +import HBS2.System.Logger.Simple + +import Codec.Serialise +import Control.Concurrent.STM (flushTQueue) +import Control.Monad.Identity +import Control.Monad.Trans.Maybe +import Network.ByteOrder qualified as N +import Crypto.Saltine.Core.Box (Keypair(..),CombinedKey) +import Crypto.Saltine.Class qualified as SA +import Crypto.Saltine.Core.Box qualified as PKE +import Data.Bits +import Data.ByteArray.Hash qualified as BA +import Data.ByteArray.Hash (SipHash(..), SipKey(..)) +import Data.ByteString.Lazy (ByteString) +import Data.ByteString.Lazy qualified as LBS +import Data.ByteString qualified as BS +import Data.HashMap.Strict (HashMap) +import Data.HashMap.Strict qualified as HashMap +import Data.HashSet qualified as HashSet +import Data.Maybe +import Data.Time.Clock.POSIX +import Data.Word +import System.Random +import System.IO.Unsafe (unsafePerformIO) +import UnliftIO + +heySeed :: Word8 +heySeed = 117 + +newtype NonceA = NonceA { fromNonceA :: Word16 } + deriving newtype (Eq,Ord,Show,Pretty,Real,Num,Enum,Integral,Hashable) + deriving stock Generic + +type FlowKey = Word32 + +instance Serialise NonceA + +mySipHash :: Integral a => BS.ByteString -> a +mySipHash s = BA.sipHash (SipKey a b) s + & \(SipHash w) -> fromIntegral w + where + a = 3857206264 + b = 1307114574 + + +-- NOTE: key-update-on-fly +-- мы можем на ходу менять ключи: +-- меняем nonceA, перегенеряем ключ, больше ничего не трогаем: +-- тогда пакеты посланные для старого nonceA можно будет расшифровать, +-- а шифровать уже для нового. +-- +-- Таким образом, хост может иметь много flow с разными +-- нонсами одновременно +-- + + +data ByPassOpts e = + ByPassOpts + { byPassEnabled :: Bool + , byPassKeyAllowed :: PubKey 'Sign (Encryption e) -> IO Bool + , byPassTimeRange :: Maybe (Int, Int) + } + +data ByPassStat = + ByPassStat + { statBypassed :: Int + , statEncrypted :: Int + , statDecrypted :: Int + , statDecryptFails :: Int + , statSent :: Int + , statReceived :: Int + , statFlowNum :: Int + , statPeers :: Int + , statAuthFail :: Int + } + deriving stock (Show,Generic) + +instance Serialise ByPassStat + +data ByPass e them = + ByPass + { opts :: ByPassOpts e + , self :: Peer e + , pks :: PubKey 'Sign (Encryption e) + , sks :: PrivKey 'Sign (Encryption e) + , pke :: PubKey 'Encrypt (Encryption e) + , ske :: PrivKey 'Encrypt (Encryption e) + , proxied :: them + , nonceA :: NonceA + , delayed :: TQueue (To e, ByteString) + , heySent :: TVar (HashMap (Peer e) TimeSpec) + , noncesByPeer :: TVar (HashMap (Peer e) NonceA) + , flowKeys :: TVar (HashMap FlowKey CombinedKey) + , bypassed :: TVar Int + , encrypted :: TVar Int + , decrypted :: TVar Int + , decryptFails :: TVar Int + , sentNum :: TVar Int + , recvNum :: TVar Int + , authFail :: TVar Int + } + +type ForByPass e = ( Hashable (Peer e) + , Pretty (Peer e) + , Eq (PubKey 'Sign (Encryption e)) + , Serialise (PubKey 'Sign (Encryption e)) + , PrivKey 'Encrypt (Encryption e) ~ PKE.SecretKey + , PubKey 'Encrypt (Encryption e) ~ PKE.PublicKey + , ForSignedBox e + ) + + +data HEYBox e = + HEYBox Int (PubKey 'Encrypt (Encryption e)) + deriving stock Generic + +instance ForByPass e => Serialise (HEYBox e) + +data EncryptHandshake e = + HEY + { heyNonceA :: NonceA + , heyBox :: SignedBox (HEYBox e) e + } + deriving stock (Generic) + +instance ForByPass e => Serialise (EncryptHandshake e) + +getStat :: forall e w m . ( ForByPass e + , MonadIO m + ) + => ByPass e w + -> m ByPassStat +getStat bus = liftIO $ + ByPassStat <$> readTVarIO (bypassed bus) + <*> readTVarIO (encrypted bus) + <*> readTVarIO (decrypted bus) + <*> readTVarIO (decryptFails bus) + <*> readTVarIO (sentNum bus) + <*> readTVarIO (recvNum bus) + <*> (readTVarIO (flowKeys bus) <&> HashMap.size) + <*> (readTVarIO (noncesByPeer bus) <&> HashMap.size) + <*> readTVarIO (authFail bus) + +cleanupByPassMessaging :: forall e w m . ( ForByPass e + , MonadIO m + ) + => ByPass e w + -> [Peer e] + -> m () + +cleanupByPassMessaging bus pips = do + debug "cleanupByPassMessaging" + + let alive = HashSet.fromList pips + + atomically do + sent <- readTVar (heySent bus) + nonces <- readTVar (noncesByPeer bus) + flows <- readTVar (flowKeys bus) + + let livePeers = [ (k,v) + | (k,v) <- HashMap.toList nonces + , k `HashSet.member` alive + ] & HashMap.fromList + + let liveSent = HashMap.filterWithKey (\k _ -> k `HashMap.member` livePeers) sent + + let liveFk = [ makeKey (nonceA bus) nonce + | nonce <- HashMap.elems livePeers + ] & HashSet.fromList + + let liveFlows = HashMap.filterWithKey (\k _ -> k `HashSet.member` liveFk) flows + + writeTVar (heySent bus) liveSent + writeTVar (noncesByPeer bus) livePeers + writeTVar (flowKeys bus) liveFlows + + +byPassDef :: ByPassOpts e +byPassDef = + ByPassOpts + { byPassEnabled = True + , byPassKeyAllowed = const $ pure True + , byPassTimeRange = Nothing + } + +newByPassMessaging :: forall e w m . ( ForByPass e + , MonadIO m + , Messaging w e ByteString + ) + => ByPassOpts e + -> w + -> Peer e + -> PubKey 'Sign (Encryption e) + -> PrivKey 'Sign (Encryption e) + -> m (ByPass e w) + +newByPassMessaging o w self ps sk = do + (Keypair s p) <- liftIO PKE.newKeypair + let n = mySipHash (LBS.toStrict (serialise s)) + ByPass @e o self ps sk p s w n <$> newTQueueIO + <*> newTVarIO mempty + <*> newTVarIO mempty + <*> newTVarIO mempty + <*> newTVarIO 0 + <*> newTVarIO 0 + <*> newTVarIO 0 + <*> newTVarIO 0 + <*> newTVarIO 0 + <*> newTVarIO 0 + <*> newTVarIO 0 + +instance (ForByPass e, Messaging w e ByteString) + => Messaging (ByPass e w) e ByteString where + + sendTo bus t@(To whom) f m = do + + mkey <- lookupEncKey bus whom + + atomically $ modifyTVar (sentNum bus) succ + + case mkey of + Just fck -> do + sendTo (proxied bus) t f =<< encryptMessage bus fck m + + Nothing -> do + -- let ByPassOpts{..} = opts bus + + if False then do + atomically $ writeTQueue (delayed bus) (t,m) + else do + trace $ "bypassed message to" <+> pretty whom + atomically $ modifyTVar (bypassed bus) succ + sendTo (proxied bus) t f m + + -- TODO: fix-timeout-hardcode + withHeySent bus 30 whom do + sendHey bus whom + + receive bus f = do + msgs <- receive (proxied bus) f + + q <- newTQueueIO + + -- TODO: run-concurrently + for_ msgs $ \(From who, mess) -> runMaybeT do + + atomically $ modifyTVar (recvNum bus) succ + + hshake <- processHey who mess + + guard (not hshake) + + msg <- tryDecryptMessage bus mess + + case msg of + Just demsg -> do + atomically $ writeTQueue q (From who, demsg) + + Nothing -> do + withHeySent bus 60 who do + sendHey bus who + + atomically $ writeTQueue q (From who, mess) + + liftIO $ atomically $ flushTQueue q + + where + processHey orig bs = isJust <$> runMaybeT do + + let o = opts bus + + let (code, hbs) = runCodeLazy bs + + -- FIXME: check-code + guard ( code == Just heySeed ) + + debug $ "HEY CODE:" <+> pretty code + + guard (not (LBS.null hbs)) + + hshake <- toMPlus (deserialiseOrFail @(EncryptHandshake e) hbs) + + case hshake of + HEY{..} -> do-- void $ runMaybeT do + debug $ "GOT HEY MESSAGE" <+> parens (pretty code) <+> pretty heyNonceA + + -- FIXME: check-if-key-authorized + + let mbx = unboxSignedBox0 heyBox + + when (isNothing mbx) do + debug $ "HEY: failed to unbox" <+> pretty heyNonceA <+> pretty orig + + n <- toMPlus mbx + + -- FIXME: authorize-pk-right-here + (pks, HEYBox t puk) <- toMPlus mbx + + let dt = byPassTimeRange o + + allowed <- liftIO $ byPassKeyAllowed o pks + now <- liftIO getPOSIXTime <&> round + let actual = maybe1 dt True (\(ta, tb) -> t >= now - ta && t <= now + tb) + + let authorized = allowed && actual + + unless authorized do + atomically $ modifyTVar (authFail bus) succ + warn $ "ByPass:" <+> "NOT AUTHORIZED" <+> pretty orig + + when authorized do + debug $ "ByPass:" <+> "AUTHORIZED" <+> pretty now <+> pretty orig + + guard authorized + + let fk = makeKey (nonceA bus) heyNonceA + + here <- readTVarIO (flowKeys bus) <&> HashMap.member fk + + updatePeerNonce bus orig heyNonceA + + unless here do + + let ck = PKE.beforeNM (ske bus) puk + + debug $ "HEY: CK" <+> pretty (nonceA bus) + <+> pretty fk + <+> pretty (hashObject @HbSync (SA.encode ck)) + + atomically $ do + modifyTVar (flowKeys bus) (HashMap.insert fk ck) + + withHeySent bus 30 orig do + sendHey bus orig + + pure hshake + +makeKey :: NonceA -> NonceA -> FlowKey +makeKey a b = runIdentity do + let aa = fromIntegral a :: FlowKey + let bb = fromIntegral b :: FlowKey + + let (f0,f1) = if aa < bb then (aa,bb) else (bb,aa) + + pure $ (f0 `shiftL` 16) .|. f1 + + +sendHey :: forall e w m . ( ForByPass e + , Messaging w e ByteString + , MonadIO m + ) + => ByPass e w + -> Peer e + -> m () + +sendHey bus whom = do + + pref <- randomPrefix (PrefixMethod1 4 11 heySeed) <&> toLazyByteString + + let (code, _) = runCodeLazy pref + + ts <- liftIO getPOSIXTime <&> round + + let hbox = HEYBox @e ts (pke bus) + let box = makeSignedBox @e (pks bus) (sks bus) hbox + let hey = HEY @e (nonceA bus) box + let msg = pref <> serialise hey + + debug $ "SEND HEY" <+> pretty (heyNonceA hey) + <+> parens ("seed" <+> pretty code) + <+> pretty whom + <+> pretty (LBS.length msg) + + sendTo (proxied bus) (To whom) (From (self bus)) msg + +withHeySent :: forall e w m . (MonadIO m, ForByPass e) + => ByPass e w + -> Timeout 'Seconds + -> Peer e + -> m () + -> m () + +withHeySent w ts pip m = do + now <- getTimeCoarse + + t0 <- readTVarIO (heySent w) <&> HashMap.lookup pip + <&> fromMaybe 0 + + let elapsed = toNanoSeconds $ TimeoutTS (now - t0) + + when ( elapsed >= toNanoSeconds ts ) do + atomically $ modifyTVar (heySent w) (HashMap.insert pip now) + m + + +updatePeerNonce :: forall e w m . ( ForByPass e + , MonadIO m + ) + => ByPass e w + -> Peer e + -> NonceA + -> m () + +updatePeerNonce bus pip nonce = do + atomically $ modifyTVar (noncesByPeer bus) (HashMap.insert pip nonce) + +lookupEncKey :: (ForByPass e, MonadIO m) => ByPass e w -> Peer e -> m (Maybe (FlowKey, CombinedKey)) +lookupEncKey bus whom = runMaybeT do + nonce <- MaybeT $ readTVarIO (noncesByPeer bus) <&> HashMap.lookup whom + let fk = makeKey nonce (nonceA bus) + ck <- MaybeT $ readTVarIO (flowKeys bus) <&> HashMap.lookup fk + pure (fk, ck) + + +typicalNonceLength :: Integral a => a +typicalNonceLength = unsafePerformIO PKE.newNonce & SA.encode & BS.length & fromIntegral +{-# NOINLINE typicalNonceLength #-} + +newtype ByPassNonce = ByPassNonce { unByPassNonce :: PKE.Nonce } + +instance NonceFrom ByPassNonce Word32 where + nonceFrom a = ByPassNonce nonce + where + n = typicalNonceLength + nonce = fromJust (SA.decode s) + s = BS.take n (N.bytestring32 a <> BS.replicate n 0) + + +tryDecryptMessage :: (MonadIO m, ForByPass e) + => ByPass e w + -> ByteString + -> m (Maybe ByteString) + +tryDecryptMessage bus bs = runMaybeT do + + let (hdr, body) = LBS.splitAt 8 bs + + guard (LBS.length hdr == 8) + + (fk, wnonce) <- liftIO $ N.withReadBuffer (LBS.toStrict hdr) $ \buf -> do + (,) <$> N.read32 buf <*> N.read32 buf + + let bnonce = nonceFrom @ByPassNonce wnonce + + ck <- MaybeT $ readTVarIO (flowKeys bus) <&> HashMap.lookup fk + + let dmess = PKE.boxOpenAfterNM ck (unByPassNonce bnonce) (LBS.toStrict body) <&> LBS.fromStrict + + atomically do + maybe1 dmess + (modifyTVar (decryptFails bus) succ) + (const $ modifyTVar (decrypted bus) succ) + + toMPlus dmess + + +encryptMessage :: (MonadIO m, ForByPass e) + => ByPass e w + -> (FlowKey, CombinedKey) + -> ByteString + -> m ByteString + +encryptMessage bus (fk, ck) bs = do + + atomically $ modifyTVar (encrypted bus) succ + + wnonce <- liftIO (randomIO @Word32) + let bnonce = nonceFrom @ByPassNonce wnonce + + let ebs = PKE.boxAfterNM ck (unByPassNonce bnonce) (LBS.toStrict bs) + + let pkt = mconcat [ word32BE fk + , word32BE wnonce + , byteString ebs + ] & toLazyByteString + + pure pkt + +instance Pretty ByPassStat where + pretty (ByPassStat{..}) = + vcat [ prettyField "bypassed" statBypassed + , prettyField "encrypted" statEncrypted + , prettyField "decrypted" statDecrypted + , prettyField "decryptFails" statDecryptFails + , prettyField "sent" statSent + , prettyField "received" statReceived + , prettyField "flowNum" statFlowNum + , prettyField "peers" statPeers + , prettyField "authFail" statAuthFail + ] + where + prettyField x e = fill 15 (x <> colon) <+> pretty e + diff --git a/hbs2-core/lib/HBS2/Net/Messaging/Encrypted/RandomPrefix.hs b/hbs2-core/lib/HBS2/Net/Messaging/Encrypted/RandomPrefix.hs new file mode 100644 index 00000000..682b5ac9 --- /dev/null +++ b/hbs2-core/lib/HBS2/Net/Messaging/Encrypted/RandomPrefix.hs @@ -0,0 +1,244 @@ +{-# Language AllowAmbiguousTypes #-} +{-# Language UndecidableInstances #-} +{-# Language TypeFamilyDependencies #-} +module HBS2.Net.Messaging.Encrypted.RandomPrefix + ( module Data.ByteString.Builder + , runCodeLazy + , RandomPrefix(..) + , PrefixMethod1(..) + ) where + +import Data.Word +import Data.Bits +-- import Data.ByteString (ByteString) +import Data.ByteString qualified as BS +import Data.ByteString.Lazy qualified as LBS +import Data.ByteString.Lazy (ByteString) +import Control.Monad.State +import Control.Monad.Trans.Maybe +import Data.ByteString.Builder +import Data.Maybe +import Lens.Micro.Platform +import Data.Kind +import GHC.TypeLits +import Data.Proxy +import Safe +import Data.List.Split (chunksOf) +import System.Random hiding (next) + +data NOP +data LOADB +data SKIPBI +data ANDBI +data ORBI +data XORBI +data ADDBI +data SUBBI +data MULTBI +data REPEAT +data RET + +class Emittable a where + emit :: a -> Builder + +class (Emittable (Arg a), KnownNat (Opcode a)) => Instruction a where + type family Opcode a = (code :: Nat) | code -> a + type family Arg a :: Type + +data OP = forall a . (Instruction a, Emittable (Proxy a)) => + OP (Proxy a) (Arg a) | BYTE Word8 + +instance Instruction a => Emittable (Proxy a) where + emit _ = word8 . fromIntegral $ natVal (Proxy @(Opcode a)) + +instance Emittable OP where + emit (OP op arg) = emit op <> emit arg + emit (BYTE w) = word8 w + +instance Emittable () where + emit = mempty + +instance Emittable Word8 where + emit = word8 + +instance Emittable b => Emittable [b] where + emit xs= mconcat (fmap emit xs) + +instance Instruction NOP where + type instance Opcode NOP = 0xFE + type instance Arg NOP = () + +instance Instruction LOADB where + type instance Opcode LOADB = 0x01 + type instance Arg LOADB = Word8 + +instance Instruction SKIPBI where + type instance Opcode SKIPBI = 0x02 + type instance Arg SKIPBI = Word8 + +instance Instruction ORBI where + type instance Opcode ORBI= 0x03 + type instance Arg ORBI = Word8 + +instance Instruction ANDBI where + type instance Opcode ANDBI= 0x04 + type instance Arg ANDBI = Word8 + +instance Instruction XORBI where + type instance Opcode XORBI= 0x05 + type instance Arg XORBI = Word8 + +instance Instruction ADDBI where + type instance Opcode ADDBI = 0x06 + type instance Arg ADDBI = Word8 + +instance Instruction SUBBI where + type instance Opcode SUBBI = 0x07 + type instance Arg SUBBI = Word8 + +instance Instruction MULTBI where + type instance Opcode MULTBI = 0x08 + type instance Arg MULTBI = Word8 + +instance Instruction REPEAT where + type instance Opcode REPEAT = 0xC0 + type instance Arg REPEAT = Word8 + +instance Instruction RET where + type instance Opcode RET = 0x00 + type instance Arg RET = () + +op :: forall a . Instruction a + => Arg a + -> OP + +op = OP (Proxy @a) + +byte :: Word8 -> OP +byte = BYTE + +runCodeLazy :: ByteString -> (Maybe Word8, ByteString) +runCodeLazy s = runState (execStateT (runMaybeT (go s)) Nothing) s + where + + next = MaybeT . pure . LBS.uncons + + update rest = do + lift $ lift $ put rest + pure rest + + go bs = do + r <- next bs + void $ update (snd r) + exec r >>= update + >>= go + + exec (b, rest) + | b == code @NOP = nop rest + | b == code @LOADB = loadb rest + | b == code @SKIPBI = skipbi rest + | b == code @ORBI = orbi rest + | b == code @ANDBI = andbi rest + | b == code @XORBI = xorbi rest + | b == code @ADDBI = addbi rest + | b == code @SUBBI = subbi rest + | b == code @MULTBI = multi rest + | b == code @RET = ret rest + -- | b == code @REPEAT = repeatN rest -- dangerous + | otherwise = nop rest + + ret _ = mzero -- pure + + nop = pure + + multi bs = do + (n, rest) <- next bs + apply (*) n + pure rest + + addbi bs = do + (n, rest) <- next bs + apply (+) n + pure rest + + subbi bs = do + (n, rest) <- next bs + apply (-) n + pure rest + + orbi bs = do + (n, rest) <- next bs + apply (.|.) n + pure rest + + andbi bs = do + (n, rest) <- next bs + apply (.&.) n + pure rest + + xorbi bs = do + (n, rest) <- next bs + apply xor n + pure rest + + skipbi bs = do + (n, rest) <- next bs + let r2 = LBS.drop (fromIntegral n) rest + update r2 + pure r2 + + loadb bs = do + (n, rest) <- next bs + put (Just n) + pure rest + + repeatN bs = do + (n, rest) <- next bs + + rest' <- replicateM (min 16 (fromIntegral n)) $ do + next rest >>= exec + + pure (headDef "" rest') + + apply fn n = do + st <- get + put $ Just $ fromMaybe 0 st `fn` fromIntegral n + + code :: forall a b . (Integral b, Instruction a) => b + code = fromIntegral (natVal (Proxy @(Opcode a))) + + +class (Monad m) => RandomPrefix a m where + randomPrefix :: a -> m Builder + +data PrefixMethod1 = PrefixMethod1 Int Word8 Word8 + +partsMethod1 :: Int -> Word8 -> Word8 -> [Word8] +partsMethod1 k b n = nums + where + (d, r) = n `divMod` b + nums = r : replicate (fromIntegral d) b & chunksOf k & fmap sum + +instance MonadIO m => RandomPrefix PrefixMethod1 m where + + randomPrefix (PrefixMethod1 k a x) = liftIO do + let nums = partsMethod1 k a x + me <- liftIO $ replicateM (length nums) $ randomRIO (0,2) + opcodes <- forM (zip me nums) $ \z@(_, n) -> + case fst z of + 1 -> do + let (w,p) = n `divMod` 2 + pure $ op @ADDBI p : replicate 2 (op @ADDBI w) + + 2 -> do + j <- randomIO @Word8 + pure [ op @SUBBI j, op @ADDBI (n+j) ] + + _ -> pure [ op @ADDBI n ] + + sn <- randomRIO (1,6) + bytes <- replicateM sn (randomIO @Word8) <&> fmap byte + let fin = op @SKIPBI (fromIntegral sn) : bytes + + pure $ emit $ mconcat opcodes <> fin <> [ op @RET () ] + diff --git a/hbs2-core/lib/HBS2/Net/Proto/Types.hs b/hbs2-core/lib/HBS2/Net/Proto/Types.hs index 75c1ce2b..cfb7e755 100644 --- a/hbs2-core/lib/HBS2/Net/Proto/Types.hs +++ b/hbs2-core/lib/HBS2/Net/Proto/Types.hs @@ -29,6 +29,9 @@ import Data.ByteString (ByteString) -- e -> Transport (like, UDP or TChan) -- p -> L4 Protocol (like Ping/Pong) +class NonceFrom nonce a where + nonceFrom :: a -> nonce + data CryptoAction = Sign | Encrypt data GroupKeyScheme = Symm | Asymm diff --git a/hbs2-core/lib/HBS2/Prelude.hs b/hbs2-core/lib/HBS2/Prelude.hs index 1ed3937b..30cb280c 100644 --- a/hbs2-core/lib/HBS2/Prelude.hs +++ b/hbs2-core/lib/HBS2/Prelude.hs @@ -1,4 +1,3 @@ -{-# Language FunctionalDependencies #-} module HBS2.Prelude ( module Data.String , module Safe @@ -18,7 +17,7 @@ module HBS2.Prelude , ToByteString(..) , FromByteString(..) , Text.Text - , (&), (<&>) + , (&), (<&>), for_, for ) where import Data.Typeable as X @@ -32,6 +31,9 @@ import Control.Monad (guard,when,unless,MonadPlus(..)) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Maybe + +import Data.Foldable(for_) +import Data.Traversable(for) import Data.Kind import Data.Function import Data.Functor diff --git a/hbs2-peer/app/ByPassWorker.hs b/hbs2-peer/app/ByPassWorker.hs new file mode 100644 index 00000000..d0601439 --- /dev/null +++ b/hbs2-peer/app/ByPassWorker.hs @@ -0,0 +1,57 @@ +module ByPassWorker where + +import HBS2.Prelude +import HBS2.Clock +import HBS2.Actors.Peer +import HBS2.Net.Messaging.Encrypted.ByPass +import HBS2.System.Logger.Simple + + +import HBS2.Net.Proto.Peer +import HBS2.Net.Proto.PeerExchange +import HBS2.Net.Proto.Sessions +import HBS2.Net.Proto.Types + +import PeerTypes + +import Control.Monad +import UnliftIO + + +byPassWorker :: ( ForByPass e + , MonadUnliftIO m + , MonadIO m + , HasPeer e + , HasPeerLocator e m + , Sessions e (KnownPeer e) m + , Expires (SessionKey e (KnownPeer e)) + ) + => ByPass e w + -> PeerEnv e + -> m () + +byPassWorker bp penv = do + + tstat <- async $ forever do + stats <- getStat bp + info $ "ByPass stats" + <> line + <> indent 2 (pretty stats) + <> line + + pause @'Seconds 60 + + link tstat + + gc <- async $ withPeerM penv $ forever do + pips <- getKnownPeers + cleanupByPassMessaging bp pips + pause @'Seconds 600 + + link gc + + void $ waitAnyCatchCancel [tstat, gc] + + + + diff --git a/hbs2-peer/app/DispatchProxy.hs b/hbs2-peer/app/DispatchProxy.hs new file mode 100644 index 00000000..49209767 --- /dev/null +++ b/hbs2-peer/app/DispatchProxy.hs @@ -0,0 +1,70 @@ +{-# Language UndecidableInstances #-} +module DispatchProxy + ( RouteFun + , newDispatchProxy + , runDispatchProxy + , Dispatched(..) + ) where + +import HBS2.Prelude.Plated +import HBS2.Net.Messaging + +import HBS2.System.Logger.Simple + +import Control.Concurrent.STM.TQueue qualified as TQ +import Data.ByteString.Lazy (ByteString) +import Control.Monad + +import UnliftIO + +data Dispatched = forall bus . Messaging bus L4Proto ByteString => Dispatched bus + +type RouteFun e = forall m . (MonadIO m) + => ByteString + -> Peer e + -> m (Maybe Dispatched) + + +data DispatchProxy = + DispatchProxy + { points :: [ (Peer L4Proto, Dispatched) ] + , route :: RouteFun L4Proto + , inbox :: TQueue (From L4Proto, ByteString) + } + +newDispatchProxy :: (MonadIO m) + => [ (Peer L4Proto, Dispatched) ] + -> RouteFun L4Proto + -> m DispatchProxy + +newDispatchProxy p r = DispatchProxy p r <$> newTQueueIO + + +runDispatchProxy :: forall m . ( MonadUnliftIO m + ) + => DispatchProxy + -> m () + +runDispatchProxy d = do + debug "runDispatchProxy" + + ps <- for (points d) $ \(pip, Dispatched mess) -> async do + forever do + receive mess (To pip) + >>= mapM_ (atomically . writeTQueue (inbox d)) + + mapM_ link ps + void $ waitAnyCatchCancel ps + + +instance Messaging DispatchProxy L4Proto ByteString where + + sendTo bus t@(To whom) f m = do + route bus m whom >>= maybe none sendRouted + where + sendRouted (Dispatched target) = sendTo target t f m + + receive bus _ = do + void $ atomically $ peekTQueue (inbox bus) + liftIO $ atomically $ TQ.flushTQueue (inbox bus) + diff --git a/hbs2-peer/app/DownloadQ.hs b/hbs2-peer/app/DownloadQ.hs index d393f353..2b550f82 100644 --- a/hbs2-peer/app/DownloadQ.hs +++ b/hbs2-peer/app/DownloadQ.hs @@ -46,8 +46,7 @@ downloadQueue _ brains denv = do polling (Polling 5 20) refs $ \ref -> do missed <- findMissedBlocks sto ref - - debug $ "DownloadQ. check" <+> pretty ref <+> pretty (length missed) + trace $ "DownloadQ. check" <+> pretty ref <+> pretty (length missed) when (null missed) do delDownload @e brains ref diff --git a/hbs2-peer/app/PeerMain.hs b/hbs2-peer/app/PeerMain.hs index 422d1d1b..68c2976d 100644 --- a/hbs2-peer/app/PeerMain.hs +++ b/hbs2-peer/app/PeerMain.hs @@ -21,11 +21,10 @@ import HBS2.Net.IP.Addr import HBS2.Net.Messaging.UDP import HBS2.Net.Messaging.TCP import HBS2.Net.Messaging.Unix +import HBS2.Net.Messaging.Encrypted.ByPass import HBS2.Net.PeerLocator import HBS2.Net.Proto as Proto import HBS2.Net.Proto.Definition --- import HBS2.Net.Proto.Dialog -import HBS2.Net.Proto.EncryptionHandshake import HBS2.Net.Proto.Event.PeerExpired import HBS2.Net.Proto.Peer import HBS2.Net.Proto.PeerAnnounce @@ -43,6 +42,7 @@ import HBS2.System.Logger.Simple hiding (info) import Brains import BrainyPeerLocator +import ByPassWorker import PeerTypes import BlockDownload import CheckBlockAnnounce (checkBlockAnnounce) @@ -52,13 +52,10 @@ import PeerInfo import PeerConfig import Bootstrap import CheckMetrics -import EncryptionKeys import RefLog qualified import RefLog (reflogWorker) import HttpWorker -import ProxyMessaging --- import PeerMain.DialogCliCommand --- import PeerMain.Dialog.Server +import DispatchProxy import PeerMeta import CLI.Common import CLI.RefChan @@ -233,6 +230,7 @@ runCLI = do <> command "download" (info pDownload (progDesc "download management")) <> command "poll" (info pPoll (progDesc "polling management")) <> command "log" (info pLog (progDesc "set logging level")) + <> command "bypass" (info pByPass (progDesc "bypass")) -- FIXME: bring-back-dialogue-over-separate-socket -- <> command "dial" (info pDialog (progDesc "dialog commands")) ) @@ -468,6 +466,16 @@ runCLI = do delta = now - fromIntegral u diff = formatTime defaultTimeLocale "%d:%H:%M:%S" delta + pByPass = hsubparser ( command "show" (info pByPassShow (progDesc "show bypass info" )) + ) + + pByPassShow = do + rpc <- pRpcCommon + pure $ withMyRPC @PeerAPI rpc $ \caller -> do + void $ runMaybeT do + d <- toMPlus =<< callService @RpcByPassInfo caller () + liftIO $ print $ pretty d + refP :: ReadM (PubKey 'Sign HBS2Basic) refP = maybeReader fromStringMay @@ -629,6 +637,8 @@ runPeer opts = U.handle (\e -> myException e udp <- async $ runMessagingUDP mess + let udpAddr = getOwnPeer mess + mcast <- newMessagingUDPMulticast defLocalMulticast `orDie` "Can't start RPC listener" @@ -652,43 +662,36 @@ runPeer opts = U.handle (\e -> myException e void $ async $ runMessagingTCP tcpEnv pure $ Just tcpEnv - (proxy, penv) <- mdo - proxy <- newProxyMessaging mess tcp >>= \proxy' -> pure proxy' - { _proxy_getEncryptionKey = \peer -> do - mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $ - withPeerM penv $ find (KnownPeerKey peer) id - mkey <- join <$> forM mencKeyID \encKeyID -> - getEncryptionKey proxy encKeyID - case mkey of - Nothing -> - trace1 $ "ENCRYPTION empty getEncryptionKey" - <+> pretty peer <+> viaShow mencKeyID - Just k -> - trace1 $ "ENCRYPTION success getEncryptionKey" - <+> pretty peer <+> viaShow mencKeyID <+> viaShow k - pure mkey + let mudp = Just $ Dispatched mess - , _proxy_clearEncryptionKey = \peer -> do - mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $ - withPeerM penv $ find (KnownPeerKey peer) id - forM_ mencKeyID \encKeyID -> setEncryptionKey proxy peer encKeyID Nothing - -- deletePeerAsymmKey brains peer - forM_ mencKeyID \encKeyID -> - deletePeerAsymmKey' brains (show encKeyID) + let tcpaddr = view tcpOwnPeer <$> tcp - , _proxy_sendResetEncryptionKeys = \peer -> withPeerM penv do - sendResetEncryptionKeys peer + let mtcp = Dispatched <$> tcp - , _proxy_sendBeginEncryptionExchange = \peer -> withPeerM penv do - sendBeginEncryptionExchange pc - ((pubKeyFromKeypair @s . _proxy_asymmetricKeyPair) proxy) - peer + let points = catMaybes [ (udpAddr ,) <$> mudp + , (,) <$> tcpaddr <*> mtcp + ] - } - penv <- newPeerEnv pl (AnyStorage s) (Fabriq proxy) (getOwnPeer mess) - pure (proxy, penv) + proxy <- newDispatchProxy points $ \_ pip -> case view sockType pip of + TCP -> pure mtcp + UDP -> pure mudp - proxyThread <- async $ runProxyMessaging proxy + -- TODO: get-rid-of-from-addr + -- From addres в Messaging -- пережиток, + -- ни на что не влияет, ни для чего не нужен. + -- Таскается везде со времени, когда Messaging был + -- через TQueue. Нужно его удалить повсеместно + -- Или сделать некий AnyAddr/DefaultAddr + byPass <- newByPassMessaging @L4Proto + byPassDef + proxy + (getOwnPeer mess) + (view peerSignPk pc) + (view peerSignSk pc) + + penv <- newPeerEnv pl (AnyStorage s) (Fabriq byPass) (getOwnPeer mess) + + proxyThread <- async $ runDispatchProxy proxy let peerMeta = mkPeerMeta conf penv @@ -733,8 +736,8 @@ runPeer opts = U.handle (\e -> myException e if pro then do withPeerM penv $ withDownload denv (addDownload mzero h) else do + -- FIXME: separate-process-to-mark-logs-processed withPeerM penv $ withDownload denv (processBlock h) - setReflogProcessed @e brains h let doFetchRef puk = do withPeerM penv $ do @@ -753,45 +756,6 @@ runPeer opts = U.handle (\e -> myException e let hshakeAdapter = PeerHandshakeAdapter addNewRtt - let encryptionHshakeAdapter :: - ( MonadIO m - , EventEmitter e (PeerAsymmInfo e) m - ) => EncryptionHandshakeAdapter L4Proto m s - encryptionHshakeAdapter = EncryptionHandshakeAdapter - { encHandshake_considerPeerAsymmKey = \peer mpubkey -> withPeerM penv do - mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $ - withPeerM penv $ find (KnownPeerKey peer) id - case mpubkey of - Nothing -> do - -- trace $ "ENCRYPTION delete key" <+> pretty peer <+> viaShow mencKeyID - -- deletePeerAsymmKey brains peer - forM_ mencKeyID \encKeyID -> - deletePeerAsymmKey' brains (show encKeyID) - Just pk -> do - -- emit PeerAsymmInfoKey (PeerAsymmPubKey peer pk) - let symmk = genCommonSecret @s - (privKeyFromKeypair @s (_proxy_asymmetricKeyPair proxy)) - pk - case mencKeyID of - Nothing -> do - -- insertPeerAsymmKey brains peer pk symmk - -- insertPeerAsymmKey' brains (show peer) pk symmk - trace $ "ENCRYPTION can not store key. No encKeyID" - <+> pretty peer <+> viaShow mencKeyID - Just encKeyID -> do - liftIO $ setEncryptionKey proxy peer encKeyID (Just symmk) - insertPeerAsymmKey' brains (show encKeyID) pk symmk - - , encAsymmetricKeyPair = _proxy_asymmetricKeyPair proxy - - , encGetEncryptionKey = liftIO . getEncryptionKey proxy - - } - - -- dialReqProtoAdapter <- do - -- dialReqProtoAdapterDApp <- pure dialogRoutes - -- pure DialReqProtoAdapter {..} - env <- ask pnonce <- peerNonce @e @@ -800,14 +764,6 @@ runPeer opts = U.handle (\e -> myException e addPeers @e pl ps - subscribe @e PeerExpiredEventKey \(PeerExpiredEvent peer {-mpeerData-}) -> liftIO do - mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $ - withPeerM penv $ find (KnownPeerKey peer) id - forM_ mencKeyID \encKeyID -> setEncryptionKey proxy peer encKeyID Nothing - -- deletePeerAsymmKey brains peer - forM_ mencKeyID \encKeyID -> - deletePeerAsymmKey' brains (show encKeyID) - subscribe @e PeerAnnounceEventKey $ \(PeerAnnounceEvent pip nonce) -> do unless (nonce == pnonce) $ do debug $ "Got peer announce!" <+> pretty pip @@ -936,6 +892,8 @@ runPeer opts = U.handle (\e -> myException e debug "sending local peer announce" request localMulticast (PeerAnnounce @e pnonce) + peerThread "byPassWorker" (byPassWorker byPass penv) + peerThread "httpWorker" (httpWorker conf peerMeta denv) peerThread "checkMetrics" (checkMetrics metrics) @@ -952,14 +910,10 @@ runPeer opts = U.handle (\e -> myException e peerThread "blockDownloadQ" (downloadQueue conf (SomeBrains brains) denv) - peerThread "encryptionHandshakeWorker" - (EncryptionKeys.encryptionHandshakeWorker @e conf pc encryptionHshakeAdapter) - peerThread "fillPeerMeta" (fillPeerMeta tcp tcpProbeWait) peerThread "postponedLoop" (postponedLoop denv) - peerThread "reflogWorker" (reflogWorker @e conf (SomeBrains brains) rwa) peerThread "refChanWorker" (refChanWorker @e rce (SomeBrains brains)) @@ -970,7 +924,7 @@ runPeer opts = U.handle (\e -> myException e , makeResponse (blockChunksProto adapter) , makeResponse blockAnnounceProto , makeResponse (withCredentials @e pc . peerHandShakeProto hshakeAdapter penv) - , makeResponse (withCredentials @e pc . encryptionHandshakeProto encryptionHshakeAdapter) + -- , makeResponse (withCredentials @e pc . encryptionHandshakeProto encryptionHshakeAdapter) , makeResponse peerExchangeProto , makeResponse refLogUpdateProto , makeResponse (refLogRequestProto reflogReqAdapter) @@ -1067,6 +1021,7 @@ runPeer opts = U.handle (\e -> myException e , rpcLocalMultiCast = localMulticast , rpcStorage = AnyStorage s , rpcBrains = SomeBrains brains + , rpcByPassInfo = liftIO (getStat byPass) , rpcDoFetch = liftIO . fetchHash penv denv , rpcDoRefChanHeadPost = refChanHeadPostAction , rpcDoRefChanPropose = refChanProposeAction @@ -1084,6 +1039,7 @@ runPeer opts = U.handle (\e -> myException e , makeResponse (makeServer @StorageAPI) ] + link proxyThread link rpcProto link loop @@ -1093,6 +1049,7 @@ runPeer opts = U.handle (\e -> myException e , rpcProto , ann , messMcast + , proxyThread , brainsThread ] diff --git a/hbs2-peer/app/PeerTypes.hs b/hbs2-peer/app/PeerTypes.hs index cd8d3507..eb93fcf2 100644 --- a/hbs2-peer/app/PeerTypes.hs +++ b/hbs2-peer/app/PeerTypes.hs @@ -3,7 +3,10 @@ {-# Language UndecidableInstances #-} {-# Language AllowAmbiguousTypes #-} {-# Language MultiWayIf #-} -module PeerTypes where +module PeerTypes + ( module PeerTypes + , module HBS2.Net.PeerLocator + ) where import HBS2.Actors.Peer import HBS2.Clock diff --git a/hbs2-peer/app/ProxyMessaging.hs b/hbs2-peer/app/ProxyMessaging.hs deleted file mode 100644 index 69076054..00000000 --- a/hbs2-peer/app/ProxyMessaging.hs +++ /dev/null @@ -1,260 +0,0 @@ -{-# Language TemplateHaskell #-} -module ProxyMessaging - ( ProxyMessaging(..) - , newProxyMessaging - , runProxyMessaging - , sendToPlainProxyMessaging - , getEncryptionKey - , setEncryptionKey - , encryptionKeyIDKeyFromPeerData - ) where - -import HBS2.Prelude.Plated -import HBS2.Net.Messaging -import HBS2.Clock -import HBS2.Crypto -import HBS2.Net.Auth.Credentials -import HBS2.Net.Proto.Definition () -import HBS2.Net.Proto.Peer -import HBS2.Net.Proto.Types -import HBS2.Net.Messaging.UDP -import HBS2.Net.Messaging.TCP - -import HBS2.System.Logger.Simple - -import PeerTypes - -import Crypto.Saltine.Class as SCl -import Crypto.Saltine.Core.Box qualified as Encrypt - -import Codec.Serialise -import Control.Applicative -import Control.Arrow hiding ((<+>)) -import Control.Concurrent.Async -import Control.Concurrent.STM -import Control.Concurrent.STM.TQueue -import Control.Monad.Trans.Maybe -import Data.ByteString (ByteString) -import Data.ByteString qualified as BS -import Data.ByteString.Lazy qualified as LBS -import Data.Hashable hiding (Hashed) -import Data.Maybe -import Data.String.Conversions (cs) -import Data.List qualified as L -import Data.Map (Map) -import Data.Map qualified as Map -import Lens.Micro.Platform as Lens -import Control.Monad -import Data.HashMap.Strict (HashMap) -import Data.HashMap.Strict qualified as HashMap - -import HBS2.Data.Types.Peer - --- TODO: protocol-encryption-goes-here - -data ProxyMessaging = - ProxyMessaging - { _proxyUDP :: MessagingUDP - , _proxyTCP :: Maybe MessagingTCP - , _proxyAnswers :: TQueue (From L4Proto, LBS.ByteString) - - , _proxy_getEncryptionKey :: Peer L4Proto -> IO (Maybe (CommonSecret (Encryption L4Proto))) - , _proxy_clearEncryptionKey :: Peer L4Proto -> IO () - , _proxy_sendResetEncryptionKeys :: Peer L4Proto -> IO () - , _proxy_sendBeginEncryptionExchange :: Peer L4Proto -> IO () - - , _proxy_asymmetricKeyPair :: AsymmKeypair (Encryption L4Proto) - , _proxy_encryptionKeys :: TVar (HashMap (EncryptionKeyIDKey L4Proto) (CommonSecret (Encryption L4Proto))) - } - --- 1 нода X создаёт себе Encrypt.Keypair --- 2 подписывает из него публичный ключ ключом подписи ноды X и отправляет ноде Y --- 3 нода Y получила Публичный ключ ноды X, создала симметричный Key, --- зашифровала его для полученного Публичного ключа ноды X и отравила ей - -makeLenses 'ProxyMessaging - -newProxyMessaging :: forall m . MonadIO m - => MessagingUDP - -> Maybe MessagingTCP - -> m ProxyMessaging - -newProxyMessaging u t = liftIO do - let _proxyUDP = u - let _proxyTCP = t - _proxyAnswers <- newTQueueIO - - let _proxy_getEncryptionKey = const (pure Nothing) - let _proxy_clearEncryptionKey = const (pure ()) - let _proxy_sendResetEncryptionKeys = const (pure ()) - let _proxy_sendBeginEncryptionExchange = const (pure ()) - - _proxy_asymmetricKeyPair <- asymmNewKeypair @(Encryption L4Proto) - _proxy_encryptionKeys <- liftIO (newTVarIO mempty) - - pure ProxyMessaging {..} - ---- - -setEncryptionKey :: - ( Hashable (PubKey 'Sign (Encryption L4Proto)) - , Hashable PeerNonce - , Show (PubKey 'Sign (Encryption L4Proto)) - , Show PeerNonce - , Show (CommonSecret (Encryption L4Proto)) - , Show (EncryptionKeyIDKey L4Proto) - ) => ProxyMessaging -> Peer L4Proto -> EncryptionKeyIDKey L4Proto -> Maybe (CommonSecret (Encryption L4Proto)) -> IO () -setEncryptionKey proxy peer pd msecret = do - atomically $ modifyTVar' (_proxy_encryptionKeys proxy) $ Lens.at pd .~ msecret - case msecret of - Nothing -> trace $ "ENCRYPTION delete key" <+> pretty peer <+> viaShow pd - Just k -> trace $ "ENCRYPTION store key" <+> pretty peer <+> viaShow pd <+> viaShow k - -getEncryptionKey :: - ( Hashable (PubKey 'Sign (Encryption L4Proto)) - , Hashable PeerNonce - ) => ProxyMessaging -> EncryptionKeyIDKey L4Proto -> IO (Maybe (CommonSecret (Encryption L4Proto))) -getEncryptionKey proxy pd = - readTVarIO (_proxy_encryptionKeys proxy) <&> preview (Lens.ix pd) - ---- - -runProxyMessaging :: forall m . MonadIO m - => ProxyMessaging - -> m () - -runProxyMessaging env = liftIO do - - let udp = view proxyUDP env - let answ = view proxyAnswers env - let udpPeer = getOwnPeer udp - - u <- async $ forever do - msgs <- receive udp (To udpPeer) - atomically $ do - forM_ msgs $ writeTQueue answ - - t <- async $ maybe1 (view proxyTCP env) none $ \tcp -> do - forever do - msgs <- receive tcp (To $ view tcpOwnPeer tcp) - atomically $ do - forM_ msgs $ writeTQueue answ - - liftIO $ mapM_ waitCatch [u,t] - -instance Messaging ProxyMessaging L4Proto LBS.ByteString where - - sendTo = sendToProxyMessaging - - receive = receiveFromProxyMessaging - - -- receive bus _ = liftIO do - -- -- trace "PROXY: RECEIVE" - -- -- receive (view proxyUDP bus) w - -- let answ = view proxyAnswers bus - -- atomically $ do - -- r <- readTQueue answ - -- rs <- flushTQueue answ - -- pure (r:rs) - -sendToPlainProxyMessaging :: (MonadIO m) - => ProxyMessaging - -> To L4Proto - -> From L4Proto - -> LBS.ByteString - -> m () -sendToPlainProxyMessaging bus t@(To whom) proto msg = do - let udp = view proxyUDP bus - case view sockType whom of - UDP -> sendTo udp t proto msg - TCP -> maybe1 (view proxyTCP bus) none $ \tcp -> do - sendTo tcp t proto msg - -sendToProxyMessaging :: (MonadIO m) - => ProxyMessaging - -> To L4Proto - -> From L4Proto - -> LBS.ByteString - -> m () -sendToProxyMessaging bus t@(To whom) proto msg = do - -- sendTo (view proxyUDP bus) t proto msg - -- trace $ "PROXY: SEND" <+> pretty whom - mencKey <- liftIO $ _proxy_getEncryptionKey bus whom - cf <- case mencKey of - Nothing -> do - trace1 $ "ENCRYPTION SEND: sending plain message to" <+> pretty whom - pure id - Just k -> do - trace1 $ "ENCRYPTION SEND: sending encrypted message to" <+> pretty whom <+> "with key" <+> viaShow k - boxAfterNMLazy k <$> liftIO Encrypt.newNonce - sendToPlainProxyMessaging bus t proto (cf msg) - -receiveFromProxyMessaging :: MonadIO m - => ProxyMessaging -> To L4Proto -> m [(From L4Proto, LBS.ByteString)] -receiveFromProxyMessaging bus _ = liftIO do - -- trace "PROXY: RECEIVE" - -- receive (view proxyUDP bus) w - let answ = view proxyAnswers bus - rs <- atomically $ liftM2 (:) (readTQueue answ) (flushTQueue answ) - catMaybes <$> forM rs \(w@(From whom), msg) -> do - fmap (w, ) <$> dfm whom msg - - -- Здесь: - -- 1. У нас есть ключ сессии и мы не смогли расшифровать -> do - -- удаляем у себя ключ - -- отправляем sendBeginEncryptionExchange - -- 2. У нас (до сих пор, даже если мы давно стартовали) нет ключа сессии -> do - -- sendResetEncryptionKeys - -- просто передаём сообщение как есть - - -- В протоколе пингов: - -- 1. Если слишком долго нет ответа на ping, то удаляем у себя ключ, отправляем sendResetEncryptionKeys - -- Выполняется в PeerInfo: - -- emit PeerExpiredEventKey (PeerExpiredEvent @e p mpeerData) - - where - dfm :: Peer L4Proto -> LBS.ByteString -> IO (Maybe LBS.ByteString) - dfm = \whom msg -> liftIO $ _proxy_getEncryptionKey bus whom >>= \case - - Nothing -> do - trace1 $ "ENCRYPTION RECEIVE: we do not have a key to decode" <+> pretty whom - liftIO $ _proxy_sendBeginEncryptionExchange bus whom - pure (Just msg) - - Just k -> runMaybeT $ - -- А будем-ка мы просто передавать сообщение дальше как есть, если не смогли расшифровать - (<|> (do - - liftIO $ _proxy_clearEncryptionKey bus whom - - liftIO $ _proxy_sendResetEncryptionKeys bus whom - - trace1 $ "ENCRYPTION RECEIVE: got plain message. clearing key of" <+> pretty whom - pure msg - )) $ - do - trace $ "ENCRYPTION RECEIVE: we have a key to decode from" <+> pretty whom <+> ":" <+> viaShow k - case ((extractNonce . cs) msg) of - Nothing -> do - trace1 $ "ENCRYPTION RECEIVE: can not extract nonce from" <+> pretty whom <+> "message" <+> viaShow msg - fail "" - - Just (nonce, msg') -> - ((MaybeT . pure) (boxOpenAfterNMLazy k nonce msg') - <* (trace1 $ "ENCRYPTION RECEIVE: message successfully decoded from" <+> pretty whom) - ) - <|> - (do - (trace1 $ "ENCRYPTION RECEIVE: can not decode message from" <+> pretty whom) - fail "" - - -- -- Попытаться десериализовать сообщение как PeerPing или PeerPingCrypted - -- case deserialiseOrFail msg of - -- Right (_ :: PeerHandshake L4Proto) -> do - -- trace $ "ENCRYPTION RECEIVE: plain message decoded as PeerHandshake" <+> pretty whom - -- fail "" - -- Left _ -> do - -- trace $ "ENCRYPTION RECEIVE: failed" <+> pretty whom - -- mzero - - ) diff --git a/hbs2-peer/app/RPC2/ByPassStat.hs b/hbs2-peer/app/RPC2/ByPassStat.hs new file mode 100644 index 00000000..63f392f3 --- /dev/null +++ b/hbs2-peer/app/RPC2/ByPassStat.hs @@ -0,0 +1,21 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE UndecidableInstances #-} +module RPC2.ByPassStat where + +import HBS2.Prelude.Plated +import HBS2.Net.Proto.Service + +import HBS2.System.Logger.Simple + +import HBS2.Peer.RPC.API.Peer +import HBS2.Peer.RPC.Internal.Types + +instance (MonadIO m, HasRpcContext PeerAPI RPC2Context m) => HandleMethod m RpcByPassInfo where + + handleMethod _ = do + co <- getRpcContext @PeerAPI + debug $ "rpc.byPassInfo" + liftIO $ rpcByPassInfo co + + + diff --git a/hbs2-peer/app/RPC2/Peer.hs b/hbs2-peer/app/RPC2/Peer.hs index 627fe9ad..379df0eb 100644 --- a/hbs2-peer/app/RPC2/Peer.hs +++ b/hbs2-peer/app/RPC2/Peer.hs @@ -17,4 +17,6 @@ import RPC2.Die() import RPC2.LogLevel() import RPC2.Poll() import RPC2.Downloads() +import RPC2.ByPassStat() + diff --git a/hbs2-peer/hbs2-peer.cabal b/hbs2-peer/hbs2-peer.cabal index d147be7d..ca12790e 100644 --- a/hbs2-peer/hbs2-peer.cabal +++ b/hbs2-peer/hbs2-peer.cabal @@ -155,9 +155,9 @@ executable hbs2-peer other-modules: BlockDownload , BrainyPeerLocator + , ByPassWorker , DownloadQ , DownloadMon - , EncryptionKeys , Bootstrap , PeerInfo , PeerMain.Dialog.Server @@ -174,6 +174,7 @@ executable hbs2-peer , RPC2.Announce , RPC2.Fetch , RPC2.Die + , RPC2.ByPassStat , RPC2.LogLevel , RPC2.Peers , RPC2.PexInfo @@ -189,7 +190,7 @@ executable hbs2-peer , CheckMetrics , HttpWorker , Brains - , ProxyMessaging + , DispatchProxy , CLI.Common , CLI.RefChan diff --git a/hbs2-peer/lib/HBS2/Peer/RPC/API/Peer.hs b/hbs2-peer/lib/HBS2/Peer/RPC/API/Peer.hs index 7c2777b8..b4bb9f30 100644 --- a/hbs2-peer/lib/HBS2/Peer/RPC/API/Peer.hs +++ b/hbs2-peer/lib/HBS2/Peer/RPC/API/Peer.hs @@ -6,6 +6,7 @@ import HBS2.Net.Messaging.Unix import HBS2.Net.Proto.Service import HBS2.Data.Types.Refs (HashRef(..)) import HBS2.Actors.Peer +import HBS2.Net.Messaging.Encrypted.ByPass(ByPassStat) import HBS2.Peer.RPC.Internal.Types @@ -30,6 +31,8 @@ data RpcPollDel data RpcDownloadList data RpcDownloadDel +data RpcByPassInfo + type PeerAPI = '[ RpcPoke , RpcPing , RpcAnnounce @@ -43,6 +46,7 @@ type PeerAPI = '[ RpcPoke , RpcPollDel , RpcDownloadList , RpcDownloadDel + , RpcByPassInfo ] instance HasProtocol UNIX (ServiceProto PeerAPI UNIX) where @@ -95,6 +99,9 @@ type instance Output RpcPollDel = () type instance Input RpcLogLevel = SetLogging type instance Output RpcLogLevel = () +type instance Input RpcByPassInfo = () +type instance Output RpcByPassInfo = ByPassStat + data SetLogging = DebugOn Bool | TraceOn Bool diff --git a/hbs2-peer/lib/HBS2/Peer/RPC/Internal/Types.hs b/hbs2-peer/lib/HBS2/Peer/RPC/Internal/Types.hs index a3d8efa2..70a15238 100644 --- a/hbs2-peer/lib/HBS2/Peer/RPC/Internal/Types.hs +++ b/hbs2-peer/lib/HBS2/Peer/RPC/Internal/Types.hs @@ -6,10 +6,11 @@ module HBS2.Peer.RPC.Internal.Types import HBS2.Actors.Peer import HBS2.Net.Proto.Types -import HBS2.Storage +import HBS2.Storage() import HBS2.Data.Types.Refs (HashRef) import HBS2.Data.Types.SignedBox import HBS2.Net.Messaging.Unix +import HBS2.Net.Messaging.Encrypted.ByPass (ByPassStat) import HBS2.Net.Proto.Service import HBS2.Peer.RPC.Class import HBS2.Peer.Brains @@ -31,6 +32,7 @@ data RPC2Context = , rpcLocalMultiCast :: Peer L4Proto , rpcStorage :: AnyStorage , rpcBrains :: SomeBrains L4Proto + , rpcByPassInfo :: IO ByPassStat , rpcDoFetch :: HashRef -> IO () , rpcDoRefChanHeadPost :: HashRef -> IO () , rpcDoRefChanPropose :: (PubKey 'Sign HBS2Basic, SignedBox ByteString L4Proto) -> IO () diff --git a/hbs2-tests/hbs2-tests.cabal b/hbs2-tests/hbs2-tests.cabal index 3b235edf..1f1f450c 100644 --- a/hbs2-tests/hbs2-tests.cabal +++ b/hbs2-tests/hbs2-tests.cabal @@ -751,6 +751,60 @@ executable topsort-commits -- , vector -- , fast-logger +executable test-enc-1 + import: shared-properties + import: common-deps + default-language: Haskell2010 + + ghc-options: + -- -prof + -- -fprof-auto + + other-modules: + + -- other-extensions: + + -- type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: TestEnc.hs + + build-depends: + base, hbs2-core + -- , async + -- , attoparsec + , bytestring + -- , cache + -- , clock + , containers + , interpolatedstring-perl6 + -- , data-default + -- , data-textual + -- , directory + -- , hashable + -- , microlens-platform + -- , mtl + -- , mwc-random + , memory + , network-byte-order + -- , network + -- , network-ip + , prettyprinter + -- , random + , safe + , serialise + -- , stm + -- , streaming + , saltine + , text + , time + -- , typed-process + -- , transformers + , uniplate + , unliftio + -- , vector + -- , fast-logger + + executable create-raw-tx import: shared-properties diff --git a/hbs2-tests/test/TestEnc.hs b/hbs2-tests/test/TestEnc.hs new file mode 100644 index 00000000..cffb02e9 --- /dev/null +++ b/hbs2-tests/test/TestEnc.hs @@ -0,0 +1,238 @@ +{-# Language TemplateHaskell #-} +module Main where + +import HBS2.Prelude.Plated +import HBS2.Clock +import HBS2.Net.Proto +import HBS2.Net.Messaging.UDP +import HBS2.Net.Messaging.TCP +import HBS2.Actors.Peer +import HBS2.OrDie + +import HBS2.System.Logger.Simple + +import HBS2.Net.Messaging.Encrypted.ByPass + +import Control.Monad.Reader +import Data.ByteString.Lazy (ByteString) +import Prettyprinter +import System.IO +import Lens.Micro.Platform +import Crypto.Saltine.Core.Sign + +import Data.Int +import Codec.Serialise +-- import Control.Concurrent.Async +import System.Random +import Text.InterpolatedString.Perl6 (qc) + +import Control.Monad.Trans.Resource +import UnliftIO.Async + +type UDP = L4Proto + +data PingPong e = Ping Int + | Pong Int + | Poke + deriving stock (Eq,Generic,Show,Read) + + +instance Serialise (PingPong e) + +instance HasProtocol UDP (PingPong UDP) where + type instance ProtocolId (PingPong UDP) = 1307114574 + type instance Encoded UDP = ByteString + decode = either (const Nothing) Just . deserialiseOrFail + encode = serialise + +pingPongHandler :: forall e m . ( MonadIO m + , Response e (PingPong e) m + , HasProtocol e (PingPong e) + , HasOwnPeer e m + , Pretty (Peer e) + ) + => Int + -> PingPong e + -> m () + +pingPongHandler n = \case + + Poke -> pure () + + Ping c -> do + self <- ownPeer @e + debug ("Ping" <+> pretty self <+> pretty c) >> response (Pong @e c) + + Pong c | c < n -> do + self <- ownPeer @e + debug ("Pong" <+> pretty self <+> pretty c) >> response (Ping @e (succ c)) + + | otherwise -> pure () + +data PPEnv = + PPEnv + { _ppSelf :: Peer UDP + , _ppFab :: Fabriq UDP + } + +makeLenses 'PPEnv + +newtype PingPongM m a = PingPongM { fromPingPong :: ReaderT PPEnv m a } + deriving newtype ( Functor + , Applicative + , Monad + , MonadIO + , MonadUnliftIO + , MonadReader PPEnv + , MonadTrans + ) + +runPingPong :: (MonadIO m) => Peer UDP -> Fabriq UDP -> PingPongM m a -> m a +runPingPong pip udp m = runReaderT (fromPingPong m) (PPEnv pip udp) + +instance Monad m => HasFabriq UDP (PingPongM m) where + getFabriq = asks (view ppFab) + +instance Monad m => HasOwnPeer UDP (PingPongM m) where + ownPeer = asks (view ppSelf) + +instance HasTimeLimits UDP (PingPong UDP) IO where + tryLockForPeriod _ _ = pure True + +tracePrefix :: SetLoggerEntry +tracePrefix = logPrefix "[trace] " + +debugPrefix :: SetLoggerEntry +debugPrefix = logPrefix "[debug] " + +errorPrefix :: SetLoggerEntry +errorPrefix = logPrefix "[error] " + +warnPrefix :: SetLoggerEntry +warnPrefix = logPrefix "[warn] " + +noticePrefix :: SetLoggerEntry +noticePrefix = logPrefix "[RT] " + + +testUDP :: IO () +testUDP = runResourceT do + + let o = byPassDef + + udp1 <- newMessagingUDP False (Just "127.0.0.1:10001") `orDie` "Can't start listener on 10001" + udp2 <- newMessagingUDP False (Just "127.0.0.1:10002") `orDie` "Can't start listener on 10002" + + Keypair s1 p1 <- liftIO newKeypair + pass1 <- newByPassMessaging @UDP o udp1 (getOwnPeer udp1) p1 s1 + + Keypair s2 p2 <- liftIO newKeypair + pass2 <- newByPassMessaging @UDP o udp2 (getOwnPeer udp2) p2 s2 + + m1 <- async $ runMessagingUDP udp1 + m2 <- async $ runMessagingUDP udp2 + + let own1 = getOwnPeer udp1 + let own2 = getOwnPeer udp2 + + peer1 <- async $ runPingPong own1 (Fabriq pass1) do + proto <- async $ runProto @UDP + [ makeResponse (pingPongHandler 10) + ] + link proto + forever (pause @'Seconds 10) + + + peer2 <- async $ runPingPong own2 (Fabriq pass2) do + proto <- async $ runProto @UDP + [ makeResponse (pingPongHandler 10) + ] + link proto + request (getOwnPeer udp1) (Ping @UDP 0) + forever (pause @'Seconds 10) + + mapM_ wait [peer1,peer2,m1,m2] + + +testTCP :: IO () +testTCP = runResourceT do + + let o = byPassDef + + pn1 <- liftIO $ randomIO @Int8 <&> ((11000 +) . fromIntegral) + pn2 <- liftIO $ randomIO @Int8 <&> ((11000 +). fromIntegral) + + let addr1 = fromString [qc|tcp://127.0.0.1:{pn1}|] + let addr2 = fromString [qc|tcp://127.0.0.1:{pn2}|] + + debug $ "ADDR1" <+> pretty addr1 + debug $ "ADDR2" <+> pretty addr2 + + me1 <- newMessagingTCP addr1 + me2 <- newMessagingTCP addr2 + + m1 <- async $ runMessagingTCP me1 + m2 <- async $ runMessagingTCP me2 + + let peer1 = view tcpOwnPeer me1 + let peer2 = view tcpOwnPeer me2 + + + Keypair s1 p1 <- liftIO newKeypair + pass1 <- newByPassMessaging o me1 peer1 p1 s1 + + Keypair s2 p2 <- liftIO newKeypair + pass2 <- newByPassMessaging o me2 peer2 p2 s2 + + peerThread1 <- async $ runPingPong peer1 (Fabriq pass1) do + proto <- async $ runProto @L4Proto + [ makeResponse (pingPongHandler 10) + ] + link proto + + request peer2 (Poke @L4Proto) + + pause @'Seconds 1 + + request peer2 (Ping @L4Proto 0) + + forever (pause @Seconds 10) + + + peerThread2 <- async $ runPingPong peer2 (Fabriq pass2) do + proto <- async $ runProto @L4Proto + [ makeResponse (pingPongHandler 10) + ] + link proto + forever (pause @Seconds 10) + + waiter <- async $ do + pause @'Seconds 3 + + void $ waitAnyCatchCancel [peerThread1,peerThread2,m1,m2,waiter] + + stat1 <- getStat pass1 + stat2 <- getStat pass2 + + debug "testUDP done" + + liftIO $ print $ "peer1 stats" <> line <> indent 4 (pretty stat1) <> line + liftIO $ print $ "peer2 stats" <> line <> indent 4 (pretty stat2) <> line + + +main :: IO () +main = do + + liftIO $ hSetBuffering stdout LineBuffering + liftIO $ hSetBuffering stderr LineBuffering + + setLogging @DEBUG debugPrefix + setLogging @INFO defLog + setLogging @ERROR errorPrefix + setLogging @WARN warnPrefix + setLogging @NOTICE noticePrefix + setLogging @TRACE tracePrefix + + testTCP + + diff --git a/hbs2-tests/test/TestTCP.hs b/hbs2-tests/test/TestTCP.hs index 08f81479..77a1ceb8 100644 --- a/hbs2-tests/test/TestTCP.hs +++ b/hbs2-tests/test/TestTCP.hs @@ -210,7 +210,7 @@ main = do ] pp2 <- async $ runPingPong env2 do - -- request (view tcpOwnPeer env1) (Ping @L4Proto 1) + request (view tcpOwnPeer env1) (Ping @L4Proto 1) runProto @L4Proto [ makeResponse (pingPongHandler 3) ]