Compare commits

...
Author SHA1 Message Date
Evgeny Poberezkin 4268b90763 6.0.5.0 2024-09-24 12:34:46 +01:00
Evgeny fa772af6c6 agent: support socks proxy without isolate-by-auth, with and without credentials (#1320)
* agent: support socks proxy without isolate-by-auth, with and without credentials

* add unit tests

* make xftp use correct SOCKS credentials

* rename

* support ipv6 in brackets, test parsing

* constant

* textToHostMode

* space
2024-09-15 21:36:31 +01:00
Evgeny bec4e5e038 smp: reduce max message sizes (#1318) 2024-09-14 17:34:29 +01:00
Evgeny Poberezkin 24ded9e5a0 Merge branch 'master' into stable 2024-09-11 19:03:29 +01:00
Evgeny Poberezkin f5e666ae4f 6.0.4.0 2024-09-11 18:51:26 +01:00
Evgeny Poberezkin 62133ceb24 Revert "xrcp: use SHA3-256 in hybrid key agreement (#1302)"
This reverts commit 67d38090ed.
2024-09-11 18:45:44 +01:00
Evgeny 3b50e1fb7d ntf server: only use SOCKS proxy for servers without public address (#1314) 2024-09-11 18:41:40 +01:00
Evgeny 7c25b3b1e0 smp protocol: send DELD when subscribed queue is deleted (#1312)
* smp protocol: send DELD when subscribed queue is deleted

* fix, test

* refactor

* send DELD event only if the client supports it (version 10); send END otherwise

* fix test

* notify on notifier rotation

* increase test delays
2024-09-11 13:16:51 +01:00
Evgeny a70bd02c67 xftp server: round down file creation time to 1 hour (#1310) 2024-09-10 08:14:05 +01:00
sh 7000431249 scripts/servers: update stopscript (#1286)
* scripts/servers: update stopscript

* Major refactoring

* archive -> backups

* simplify logic

* sort files by timestamp
2024-09-09 10:19:17 +01:00
29 changed files with 567 additions and 153 deletions
+28
View File
@@ -1,3 +1,31 @@
# 6.0.5
SMP agent:
- support generic SOCKS proxy (without isolate-by-auth).
- reduce max message sizes
# 6.0.4
SMP server:
- better performance/memory: fewer map updates on re-subscriptions (#1297), split and reduce STM transactions (#1294)
- send DELD when subscribed queue is deleted (#1312)
- add created/updated/used date to queues to manage expiration (#1306)
XFTP server: truncate file creation time to 1 hour (#1310)
Servers:
- bind control port only to 127.0.0.1 for better security in case of firewall misconfiguration (#1280)
- reduce memory used for period stats (#1298)
Agent: process last notification from list (#1307)
- report receive file error with redirected file ID, when redirect is present (#1304)
- special error when deleted user record is not in database (#1303)
- fix race when sending a message to the deleted connection (#1296)
- support for multiple messages in a single notification
Ntf server:
- only use SOCKS proxy for servers without public address (#1314)
# 6.0.3 # 6.0.3
Agent: Agent:
+1 -1
View File
@@ -1,5 +1,5 @@
name: simplexmq name: simplexmq
version: 6.0.3.0 version: 6.0.5.0
synopsis: SimpleXMQ message broker synopsis: SimpleXMQ message broker
description: | description: |
This package includes <./docs/Simplex-Messaging-Server.html server>, This package includes <./docs/Simplex-Messaging-Server.html server>,
+2 -2
View File
@@ -250,7 +250,7 @@ In pseudo-code:
``` ```
// session 1 // session 1
hostHelloSecret(1) = dhSecret(1) hostHelloSecret(1) = dhSecret(1)
sessionSecret(1) = sha3-256(dhSecret(1) || kemSecret(1)) // to encrypt session 1 data, incl. controller hello sessionSecret(1) = sha256(dhSecret(1) || kemSecret(1)) // to encrypt session 1 data, incl. controller hello
dhSecret(1) = dh(hostHelloDhKey(1), controllerInvitationDhKey(1)) dhSecret(1) = dh(hostHelloDhKey(1), controllerInvitationDhKey(1))
kemCiphertext(1) = enc(kemSecret(1), kemEncKey(1)) kemCiphertext(1) = enc(kemSecret(1), kemEncKey(1))
// kemEncKey is included in host HELLO, kemCiphertext - in controller HELLO // kemEncKey is included in host HELLO, kemCiphertext - in controller HELLO
@@ -262,7 +262,7 @@ dhSecret(n') = dh(hostHelloDhKey(n - 1), controllerDhKey(n))
// session n // session n
hostHelloSecret(n) = dhSecret(n) hostHelloSecret(n) = dhSecret(n)
sessionSecret(n) = sha3-256(dhSecret(n) || kemSecret(n)) // to encrypt session n data, incl. controller hello sessionSecret(n) = sha256(dhSecret(n) || kemSecret(n)) // to encrypt session n data, incl. controller hello
dhSecret(n) = dh(hostHelloDhKey(n), controllerDhKey(n)) dhSecret(n) = dh(hostHelloDhKey(n), controllerDhKey(n))
// controllerDhKey(n) is either from invitation or from multicast announcement // controllerDhKey(n) is either from invitation or from multicast announcement
kemCiphertext(n) = enc(kemSecret(n), kemEncKey(n)) kemCiphertext(n) = enc(kemSecret(n), kemEncKey(n))
+165 -21
View File
@@ -1,30 +1,174 @@
#!/usr/bin/env sh #!/usr/bin/env sh
set -eu set -eu
path_conf_var="/var/opt" # Common
path_conf_smp="$path_conf_var/simplex" # ------
path_conf_xftp="$path_conf_var/simplex-xftp"
path_conf_storelog_smp="$path_conf_smp/smp-server-store.log"
path_conf_storelog_xftp="$path_conf_xftp/file-server-store.log"
date="$(date -u '+%Y-%m-%dT%H:%M:%S')" date="$(date -u '+%Y-%m-%dT%H:%M:%S')"
backup_smp() { GRN='\033[0;32m'
if [ -e "$path_conf_storelog_smp" ]; then YLW='\033[1;33m'
cp "$path_conf_storelog_smp" "${path_conf_storelog_smp}.${date:-date-failed}" BLU='\033[1;34m'
fi RED='\033[0;31m'
NC='\033[0m'
path_conf_var="/var/opt"
smp_variables() {
path_conf_smp="$path_conf_var/simplex"
path_conf_smp_archive="$path_conf_smp/backups"
path_conf_smp_archive_storelog="$path_conf_smp_archive/queues"
path_conf_smp_archive_stats="$path_conf_smp_archive/stats"
path_conf_smp_archive_messages="$path_conf_smp_archive/messages"
path_conf_storelog_smp="$path_conf_smp/smp-server-store.log"
path_conf_storelog_smp_out="$path_conf_smp_archive_storelog/smp-server-store.log.${date:-date-failed}"
path_conf_stats_smp="$path_conf_smp/smp-server-stats.log"
path_conf_stats_smp_out="$path_conf_smp_archive_stats/smp-server-stats.log.${date:-date-failed}"
path_conf_messages_smp="$path_conf_smp/smp-server-messages.log"
path_conf_messages_smp_out="$path_conf_smp_archive_messages/smp-server-messages.log.${date:-date-failed}"
} }
backup_xftp() { xftp_variables() {
if [ -e "$path_conf_storelog_xftp" ]; then path_conf_xftp="$path_conf_var/simplex-xftp"
cp "$path_conf_storelog_xftp" "${path_conf_storelog_xftp}.${date:-date-failed}" path_conf_xftp_archive="$path_conf_xftp/backups"
fi
path_conf_xftp_archive_storelog="$path_conf_xftp_archive/queues"
path_conf_xftp_archive_stats="$path_conf_xftp_archive/stats"
path_conf_storelog_xftp="$path_conf_xftp/file-server-store.log"
path_conf_storelog_xftp_out="$path_conf_xftp_archive_storelog/file-server-store.log.${date:-date-failed}"
path_conf_stats_xftp="$path_conf_xftp/file-server-stats.log"
path_conf_stats_xftp_out="$path_conf_xftp_archive_stats/file-server-stats.log.${date:-date-failed}"
} }
if [ "$1" = 'smp-server' ]; then checks() {
backup_smp result=${SERVICE_RESULT:-exit-code}
elif [ "$1" = 'xftp-server' ]; then status=${EXIT_STATUS:-TERM}
backup_xftp
else case "$result" in
backup_smp success)
backup_xftp case "$status" in
fi TERM)
printf "${RED}Refusing to backup files with failed service state${NC}\n"
exit 1
;;
*)
:
;;
esac
;;
*)
printf "${RED}Refusing to backup files with failed service state${NC}\n"
exit 1
;;
esac
}
smp_check() {
if [ ! -d "$path_conf_smp_archive_storelog" ]; then
mkdir -p "$path_conf_smp_archive_storelog"
fi
if [ ! -d "$path_conf_smp_archive_messages" ]; then
mkdir -p "$path_conf_smp_archive_messages"
fi
if [ ! -d "$path_conf_smp_archive_stats" ]; then
mkdir -p "$path_conf_smp_archive_stats"
fi
}
xftp_check() {
if [ ! -d "$path_conf_xftp_archive_storelog" ]; then
mkdir -p "$path_conf_xftp_archive_storelog"
fi
if [ ! -d "$path_conf_xftp_archive_stats" ]; then
mkdir -p "$path_conf_xftp_archive_stats"
fi
}
backup() {
file="$1"
out="$2"
file_type="$3"
if [ -e "$file" ]; then
if cp "$file" "$out"; then
printf "${YLW}${file_type}${NC} ${GRN}backup successful:${NC} ${BLU}%s${NC}\n" "${out}"
else
printf "${YLW}${file_type}${NC} ${RED}backup failed!${NC}\n"
fi
fi
unset file out file_type
}
cleanup() {
directory="$1"
file_type="$2"
files_date=$(find "$directory" -type f -exec stat --format="%y" {} + | awk '{print $1}' | sort -nr | uniq | awk 'NR==2')
if [ -n "$files_date" ]; then
files=$(find "$directory" -type f -not -newermt "$files_date" -printf "%T@ %Tc %p\n" | sort -n | awk '{print $NF}')
if [ -n "$files" ]; then
printf '%s' "$files" | xargs rm -f
printf "${YLW}Old ${file_type} files${NC}${GRN} has been deleted:${NC}\n"
files_colored=$(printf '%s' "$files" | awk '{print "\033[1;34m"$0"\033[0m"}')
printf "${files_colored}\n"
fi
fi
unset directory file_type files_date files
}
smp_backup() {
backup "$path_conf_storelog_smp" "$path_conf_storelog_smp_out" 'Storelog'
backup "$path_conf_messages_smp" "$path_conf_messages_smp_out" 'Messages'
backup "$path_conf_stats_smp" "$path_conf_stats_smp_out" 'Stats'
}
smp_cleanup() {
cleanup "$path_conf_smp_archive_storelog" 'storelog'
cleanup "$path_conf_smp_archive_stats" 'stats'
cleanup "$path_conf_smp_archive_messages" 'messages'
}
xftp_backup() {
backup "$path_conf_storelog_xftp" "$path_conf_storelog_xftp_out" 'Storelog'
backup "$path_conf_stats_xftp" "$path_conf_stats_xftp_out" 'Stats'
}
xftp_cleanup() {
cleanup "$path_conf_xftp_archive_storelog" 'storelog'
cleanup "$path_conf_xftp_archive_stats" 'stats'
}
main() {
type="${1:-}"
checks
case "$type" in
smp-server)
smp_variables
smp_check
smp_backup
smp_cleanup
;;
xftp-server)
xftp_variables
xftp_check
xftp_backup
xftp_cleanup
;;
*)
printf "${YLW}Unknown server type.${NC}\n"
exit 1
;;
esac
}
main "$@"
+2 -1
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: simplexmq name: simplexmq
version: 6.0.3.0 version: 6.0.5.0
synopsis: SimpleXMQ message broker synopsis: SimpleXMQ message broker
description: This package includes <./docs/Simplex-Messaging-Server.html server>, description: This package includes <./docs/Simplex-Messaging-Server.html server>,
<./docs/Simplex-Messaging-Client.html client> and <./docs/Simplex-Messaging-Client.html client> and
@@ -610,6 +610,7 @@ test-suite simplexmq-test
CoreTests.CryptoTests CoreTests.CryptoTests
CoreTests.EncodingTests CoreTests.EncodingTests
CoreTests.RetryIntervalTests CoreTests.RetryIntervalTests
CoreTests.SOCKSSettings
CoreTests.TRcvQueuesTests CoreTests.TRcvQueuesTests
CoreTests.UtilTests CoreTests.UtilTests
CoreTests.VersionRangeTests CoreTests.VersionRangeTests
+3 -2
View File
@@ -38,6 +38,7 @@ import Simplex.Messaging.Client
defaultNetworkConfig, defaultNetworkConfig,
proxyUsername, proxyUsername,
transportClientConfig, transportClientConfig,
clientSocksCredentials,
unexpectedResponse, unexpectedResponse,
) )
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
@@ -100,7 +101,7 @@ defaultXFTPClientConfig =
getXFTPClient :: TransportSession FileResponse -> XFTPClientConfig -> (XFTPClient -> IO ()) -> IO (Either XFTPClientError XFTPClient) getXFTPClient :: TransportSession FileResponse -> XFTPClientConfig -> (XFTPClient -> IO ()) -> IO (Either XFTPClientError XFTPClient)
getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN, xftpNetworkConfig, serverVRange} disconnected = runExceptT $ do getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN, xftpNetworkConfig, serverVRange} disconnected = runExceptT $ do
let username = proxyUsername transportSession let socksCreds = clientSocksCredentials xftpNetworkConfig $ proxyUsername transportSession
ProtocolServer _ host port keyHash = srv ProtocolServer _ host port keyHash = srv
useHost <- liftEither $ chooseTransportHost xftpNetworkConfig host useHost <- liftEither $ chooseTransportHost xftpNetworkConfig host
let tcConfig = (transportClientConfig xftpNetworkConfig useHost) {alpn = clientALPN} let tcConfig = (transportClientConfig xftpNetworkConfig useHost) {alpn = clientALPN}
@@ -108,7 +109,7 @@ getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN,
clientVar <- newTVarIO Nothing clientVar <- newTVarIO Nothing
let usePort = if null port then "443" else port let usePort = if null port then "443" else port
clientDisconnected = readTVarIO clientVar >>= mapM_ disconnected clientDisconnected = readTVarIO clientVar >>= mapM_ disconnected
http2Client <- liftError' xftpClientError $ getVerifiedHTTP2Client (Just username) useHost usePort (Just keyHash) Nothing http2Config clientDisconnected http2Client <- liftError' xftpClientError $ getVerifiedHTTP2Client socksCreds useHost usePort (Just keyHash) Nothing http2Config clientDisconnected
let HTTP2Client {sessionId, sessionALPN} = http2Client let HTTP2Client {sessionId, sessionALPN} = http2Client
v = VersionXFTP 1 v = VersionXFTP 1
thServerVRange = versionToRange v thServerVRange = versionToRange v
+6 -3
View File
@@ -33,7 +33,6 @@ import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe, isJust) import Data.Maybe (fromMaybe, isJust)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, getCurrentTime) import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, getCurrentTime)
import Data.Time.Clock.System (SystemTime (..), getSystemTime)
import Data.Time.Format.ISO8601 (iso8601Show) import Data.Time.Format.ISO8601 (iso8601Show)
import Data.Word (Word32) import Data.Word (Word32)
import qualified Data.X509 as X import qualified Data.X509 as X
@@ -57,6 +56,7 @@ import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (CorrId (..), EntityId (..), RcvPublicAuthKey, RcvPublicDhKey, RecipientId, TransmissionAuth, pattern NoEntity) import Simplex.Messaging.Protocol (CorrId (..), EntityId (..), RcvPublicAuthKey, RcvPublicDhKey, RecipientId, TransmissionAuth, pattern NoEntity)
import Simplex.Messaging.Server (dummyVerifyCmd, verifyCmdAuthorization) import Simplex.Messaging.Server (dummyVerifyCmd, verifyCmdAuthorization)
import Simplex.Messaging.Server.Expiration import Simplex.Messaging.Server.Expiration
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime, getRoundedSystemTime)
import Simplex.Messaging.Server.Stats import Simplex.Messaging.Server.Stats
import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
@@ -399,7 +399,7 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
r <- runExceptT $ do r <- runExceptT $ do
sizes <- asks $ allowedChunkSizes . config sizes <- asks $ allowedChunkSizes . config
unless (size file `elem` sizes) $ throwE SIZE unless (size file `elem` sizes) $ throwE SIZE
ts <- liftIO getSystemTime ts <- liftIO getFileTime
-- TODO validate body empty -- TODO validate body empty
sId <- ExceptT $ addFileRetry st file 3 ts sId <- ExceptT $ addFileRetry st file 3 ts
rcps <- mapM (ExceptT . addRecipientRetry st 3 sId) rks rcps <- mapM (ExceptT . addRecipientRetry st 3 sId) rks
@@ -412,7 +412,7 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
let rIds = L.map (\(FileRecipient rId _) -> rId) rcps let rIds = L.map (\(FileRecipient rId _) -> rId) rcps
pure $ FRSndIds sId rIds pure $ FRSndIds sId rIds
pure $ either FRErr id r pure $ either FRErr id r
addFileRetry :: FileStore -> FileInfo -> Int -> SystemTime -> M (Either XFTPErrorType XFTPFileId) addFileRetry :: FileStore -> FileInfo -> Int -> RoundedSystemTime -> M (Either XFTPErrorType XFTPFileId)
addFileRetry st file n ts = addFileRetry st file n ts =
retryAdd n $ \sId -> runExceptT $ do retryAdd n $ \sId -> runExceptT $ do
ExceptT $ addFile st sId file ts ExceptT $ addFile st sId file ts
@@ -531,6 +531,9 @@ deleteServerFile_ FileRec {senderId, fileInfo, filePath} = do
liftIO $ atomicModifyIORef'_ (filesCount stats) (subtract 1) liftIO $ atomicModifyIORef'_ (filesCount stats) (subtract 1)
liftIO $ atomicModifyIORef'_ (filesSize stats) (subtract $ fromIntegral $ size fileInfo) liftIO $ atomicModifyIORef'_ (filesSize stats) (subtract $ fromIntegral $ size fileInfo)
getFileTime :: IO RoundedSystemTime
getFileTime = getRoundedSystemTime fileTimePrecision
expireServerFiles :: Maybe Int -> ExpirationConfig -> M () expireServerFiles :: Maybe Int -> ExpirationConfig -> M ()
expireServerFiles itemDelay expCfg = do expireServerFiles itemDelay expCfg = do
st <- asks store st <- asks store
+10 -6
View File
@@ -17,6 +17,7 @@ module Simplex.FileTransfer.Server.Store
expiredFilePath, expiredFilePath,
getFile, getFile,
ackFile, ackFile,
fileTimePrecision,
) )
where where
@@ -25,12 +26,12 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Int (Int64) import Data.Int (Int64)
import Data.Set (Set) import Data.Set (Set)
import qualified Data.Set as S import qualified Data.Set as S
import Data.Time.Clock.System (SystemTime (..))
import Simplex.FileTransfer.Protocol (FileInfo (..), SFileParty (..), XFTPFileId) import Simplex.FileTransfer.Protocol (FileInfo (..), SFileParty (..), XFTPFileId)
import Simplex.FileTransfer.Transport (XFTPErrorType (..)) import Simplex.FileTransfer.Transport (XFTPErrorType (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId) import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime (..))
import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Util (ifM, ($>>=)) import Simplex.Messaging.Util (ifM, ($>>=))
@@ -46,9 +47,12 @@ data FileRec = FileRec
fileInfo :: FileInfo, fileInfo :: FileInfo,
filePath :: TVar (Maybe FilePath), filePath :: TVar (Maybe FilePath),
recipientIds :: TVar (Set RecipientId), recipientIds :: TVar (Set RecipientId),
createdAt :: SystemTime createdAt :: RoundedSystemTime
} }
fileTimePrecision :: Int64
fileTimePrecision = 3600 -- truncate creation time to 1 hour
data FileRecipient = FileRecipient RecipientId RcvPublicAuthKey data FileRecipient = FileRecipient RecipientId RcvPublicAuthKey
instance StrEncoding FileRecipient where instance StrEncoding FileRecipient where
@@ -62,14 +66,14 @@ newFileStore = do
usedStorage <- newTVarIO 0 usedStorage <- newTVarIO 0
pure FileStore {files, recipients, usedStorage} pure FileStore {files, recipients, usedStorage}
addFile :: FileStore -> SenderId -> FileInfo -> SystemTime -> STM (Either XFTPErrorType ()) addFile :: FileStore -> SenderId -> FileInfo -> RoundedSystemTime -> STM (Either XFTPErrorType ())
addFile FileStore {files} sId fileInfo createdAt = addFile FileStore {files} sId fileInfo createdAt =
ifM (TM.member sId files) (pure $ Left DUPLICATE_) $ do ifM (TM.member sId files) (pure $ Left DUPLICATE_) $ do
f <- newFileRec sId fileInfo createdAt f <- newFileRec sId fileInfo createdAt
TM.insert sId f files TM.insert sId f files
pure $ Right () pure $ Right ()
newFileRec :: SenderId -> FileInfo -> SystemTime -> STM FileRec newFileRec :: SenderId -> FileInfo -> RoundedSystemTime -> STM FileRec
newFileRec senderId fileInfo createdAt = do newFileRec senderId fileInfo createdAt = do
recipientIds <- newTVar S.empty recipientIds <- newTVar S.empty
filePath <- newTVar Nothing filePath <- newTVar Nothing
@@ -120,8 +124,8 @@ getFile st party fId = case party of
expiredFilePath :: FileStore -> XFTPFileId -> Int64 -> STM (Maybe (Maybe FilePath)) expiredFilePath :: FileStore -> XFTPFileId -> Int64 -> STM (Maybe (Maybe FilePath))
expiredFilePath FileStore {files} sId old = expiredFilePath FileStore {files} sId old =
TM.lookup sId files TM.lookup sId files
$>>= \FileRec {filePath, createdAt} -> $>>= \FileRec {filePath, createdAt = RoundedSystemTime createdAt} ->
if systemSeconds createdAt < old if createdAt + fileTimePrecision < old
then Just <$> readTVar filePath then Just <$> readTVar filePath
else pure Nothing else pure Nothing
+3 -3
View File
@@ -28,18 +28,18 @@ import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map) import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Time.Clock.System (SystemTime)
import Simplex.FileTransfer.Protocol (FileInfo (..)) import Simplex.FileTransfer.Protocol (FileInfo (..))
import Simplex.FileTransfer.Server.Store import Simplex.FileTransfer.Server.Store
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId) import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime)
import Simplex.Messaging.Server.StoreLog import Simplex.Messaging.Server.StoreLog
import Simplex.Messaging.Util (bshow, whenM) import Simplex.Messaging.Util (bshow, whenM)
import System.Directory (doesFileExist, renameFile) import System.Directory (doesFileExist, renameFile)
import System.IO import System.IO
data FileStoreLogRecord data FileStoreLogRecord
= AddFile SenderId FileInfo SystemTime = AddFile SenderId FileInfo RoundedSystemTime
| PutFile SenderId FilePath | PutFile SenderId FilePath
| AddRecipients SenderId (NonEmpty FileRecipient) | AddRecipients SenderId (NonEmpty FileRecipient)
| DeleteFile SenderId | DeleteFile SenderId
@@ -64,7 +64,7 @@ instance StrEncoding FileStoreLogRecord where
logFileStoreRecord :: StoreLog 'WriteMode -> FileStoreLogRecord -> IO () logFileStoreRecord :: StoreLog 'WriteMode -> FileStoreLogRecord -> IO ()
logFileStoreRecord = writeStoreLogRecord logFileStoreRecord = writeStoreLogRecord
logAddFile :: StoreLog 'WriteMode -> SenderId -> FileInfo -> SystemTime -> IO () logAddFile :: StoreLog 'WriteMode -> SenderId -> FileInfo -> RoundedSystemTime -> IO ()
logAddFile s = logFileStoreRecord s .:. AddFile logAddFile s = logFileStoreRecord s .:. AddFile
logPutFile :: StoreLog 'WriteMode -> SenderId -> FilePath -> IO () logPutFile :: StoreLog 'WriteMode -> SenderId -> FilePath -> IO ()
+5 -8
View File
@@ -166,7 +166,7 @@ import Simplex.Messaging.Agent.Store
import Simplex.Messaging.Agent.Store.SQLite import Simplex.Messaging.Agent.Store.SQLite
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
import Simplex.Messaging.Client (ProtocolClient (..), SMPClientError, ServerTransmission (..), ServerTransmissionBatch, temporaryClientError, unexpectedResponse) import Simplex.Messaging.Client (SMPClientError, ServerTransmission (..), ServerTransmissionBatch, temporaryClientError, unexpectedResponse)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile, CryptoFileArgs) import Simplex.Messaging.Crypto.File (CryptoFile, CryptoFileArgs)
import Simplex.Messaging.Crypto.Ratchet (PQEncryption, PQSupport (..), pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn) import Simplex.Messaging.Crypto.Ratchet (PQEncryption, PQSupport (..), pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn)
@@ -181,7 +181,7 @@ import Simplex.Messaging.Protocol (BrokerMsg, Cmd (..), ErrorType (AUTH), MsgBod
import qualified Simplex.Messaging.Protocol as SMP import qualified Simplex.Messaging.Protocol as SMP
import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport (SMPVersion, THandleParams (sessionId)) import Simplex.Messaging.Transport (SMPVersion)
import Simplex.Messaging.Util import Simplex.Messaging.Util
import Simplex.Messaging.Version import Simplex.Messaging.Version
import Simplex.RemoteControl.Client import Simplex.RemoteControl.Client
@@ -2450,17 +2450,14 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), _v, sessId
handleNotifyAck :: AM ACKd -> AM ACKd handleNotifyAck :: AM ACKd -> AM ACKd
handleNotifyAck m = m `catchAgentError` \e -> notify (ERR e) >> ack handleNotifyAck m = m `catchAgentError` \e -> notify (ERR e) >> ack
SMP.END -> SMP.END ->
atomically (TM.lookup tSess (smpClients c) $>>= (tryReadTMVar . sessionVar) >>= processEND) atomically (ifM (activeClientSession c tSess sessId) (removeSubscription c connId $> True) (pure False))
>>= notifyEnd >>= notifyEnd
where where
processEND = \case
Just (Right clnt)
| sessId == sessionId (thParams $ connectedClient clnt) ->
removeSubscription c connId $> True
_ -> pure False
notifyEnd removed notifyEnd removed
| removed = notify END >> logServer "<--" c srv rId "END" | removed = notify END >> logServer "<--" c srv rId "END"
| otherwise = logServer "<--" c srv rId "END from disconnected client - ignored" | otherwise = logServer "<--" c srv rId "END from disconnected client - ignored"
-- Possibly, we need to add some flag to connection that it was deleted
SMP.DELD -> atomically (removeSubscription c connId) >> notify DELD
SMP.ERR e -> notify $ ERR $ SMP (B.unpack $ strEncode srv) e SMP.ERR e -> notify $ ERR $ SMP (B.unpack $ strEncode srv) e
r -> unexpected r r -> unexpected r
where where
+7 -4
View File
@@ -277,14 +277,14 @@ supportedSMPAgentVRange = mkVersionRange minSupportedSMPAgentVersion currentSMPA
e2eEncConnInfoLength :: VersionSMPA -> PQSupport -> Int e2eEncConnInfoLength :: VersionSMPA -> PQSupport -> Int
e2eEncConnInfoLength v = \case e2eEncConnInfoLength v = \case
-- reduced by 3726 (roughly the increase of message ratchet header size + key and ciphertext in reply link) -- reduced by 3726 (roughly the increase of message ratchet header size + key and ciphertext in reply link)
PQSupportOn | v >= pqdrSMPAgentVersion -> 11122 PQSupportOn | v >= pqdrSMPAgentVersion -> 11106
_ -> 14848 _ -> 14832
e2eEncAgentMsgLength :: VersionSMPA -> PQSupport -> Int e2eEncAgentMsgLength :: VersionSMPA -> PQSupport -> Int
e2eEncAgentMsgLength v = \case e2eEncAgentMsgLength v = \case
-- reduced by 2222 (the increase of message ratchet header size) -- reduced by 2222 (the increase of message ratchet header size)
PQSupportOn | v >= pqdrSMPAgentVersion -> 13634 PQSupportOn | v >= pqdrSMPAgentVersion -> 13618
_ -> 15856 _ -> 15840
-- | SMP agent event -- | SMP agent event
type ATransmission = (ACorrId, AEntityId, AEvt) type ATransmission = (ACorrId, AEntityId, AEvt)
@@ -344,6 +344,7 @@ data AEvent (e :: AEntity) where
INFO :: PQSupport -> ConnInfo -> AEvent AEConn INFO :: PQSupport -> ConnInfo -> AEvent AEConn
CON :: PQEncryption -> AEvent AEConn -- notification that connection is established CON :: PQEncryption -> AEvent AEConn -- notification that connection is established
END :: AEvent AEConn END :: AEvent AEConn
DELD :: AEvent AEConn
CONNECT :: AProtocolType -> TransportHost -> AEvent AENone CONNECT :: AProtocolType -> TransportHost -> AEvent AENone
DISCONNECT :: AProtocolType -> TransportHost -> AEvent AENone DISCONNECT :: AProtocolType -> TransportHost -> AEvent AENone
DOWN :: SMPServer -> [ConnId] -> AEvent AENone DOWN :: SMPServer -> [ConnId] -> AEvent AENone
@@ -413,6 +414,7 @@ data AEventTag (e :: AEntity) where
INFO_ :: AEventTag AEConn INFO_ :: AEventTag AEConn
CON_ :: AEventTag AEConn CON_ :: AEventTag AEConn
END_ :: AEventTag AEConn END_ :: AEventTag AEConn
DELD_ :: AEventTag AEConn
CONNECT_ :: AEventTag AENone CONNECT_ :: AEventTag AENone
DISCONNECT_ :: AEventTag AENone DISCONNECT_ :: AEventTag AENone
DOWN_ :: AEventTag AENone DOWN_ :: AEventTag AENone
@@ -466,6 +468,7 @@ aEventTag = \case
INFO {} -> INFO_ INFO {} -> INFO_
CON _ -> CON_ CON _ -> CON_
END -> END_ END -> END_
DELD -> DELD_
CONNECT {} -> CONNECT_ CONNECT {} -> CONNECT_
DISCONNECT {} -> DISCONNECT_ DISCONNECT {} -> DISCONNECT_
DOWN {} -> DOWN_ DOWN {} -> DOWN_
+27 -6
View File
@@ -80,10 +80,12 @@ module Simplex.Messaging.Client
defaultSMPClientConfig, defaultSMPClientConfig,
defaultNetworkConfig, defaultNetworkConfig,
transportClientConfig, transportClientConfig,
clientSocksCredentials,
chooseTransportHost, chooseTransportHost,
proxyUsername, proxyUsername,
temporaryClientError, temporaryClientError,
smpProxyError, smpProxyError,
textToHostMode,
ServerTransmissionBatch, ServerTransmissionBatch,
ServerTransmission (..), ServerTransmission (..),
ClientCommand, ClientCommand,
@@ -122,10 +124,13 @@ import Data.List (find)
import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
import Data.Maybe (catMaybes, fromMaybe) import Data.Maybe (catMaybes, fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime (..), diffUTCTime, getCurrentTime) import Data.Time.Clock (UTCTime (..), diffUTCTime, getCurrentTime)
import qualified Data.X509 as X import qualified Data.X509 as X
import qualified Data.X509.Validation as XV import qualified Data.X509.Validation as XV
import Network.Socket (ServiceName) import Network.Socket (ServiceName)
import Network.Socks5 (SocksCredentials (..))
import Numeric.Natural import Numeric.Natural
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
@@ -136,7 +141,7 @@ import Simplex.Messaging.Server.QueueStore.QueueInfo
import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport import Simplex.Messaging.Transport
import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), defaultTcpConnectTimeout, runTransportClient) import Simplex.Messaging.Transport.Client (SocksAuth (..), SocksProxyWithAuth (..), TransportClientConfig (..), TransportHost (..), defaultTcpConnectTimeout, runTransportClient)
import Simplex.Messaging.Transport.KeepAlive import Simplex.Messaging.Transport.KeepAlive
import Simplex.Messaging.Transport.WebSockets (WS) import Simplex.Messaging.Transport.WebSockets (WS)
import Simplex.Messaging.Util (bshow, diffToMicroseconds, ifM, liftEitherWith, raceAny_, threadDelay', tshow, whenM) import Simplex.Messaging.Util (bshow, diffToMicroseconds, ifM, liftEitherWith, raceAny_, threadDelay', tshow, whenM)
@@ -236,6 +241,12 @@ data HostMode
HMPublic HMPublic
deriving (Eq, Show) deriving (Eq, Show)
textToHostMode :: Text -> Either String HostMode
textToHostMode = \case
"public" -> Right HMPublic
"onion" -> Right HMOnionViaSocks
s -> Left $ T.unpack $ "Invalid host_mode: " <> s
data SocksMode data SocksMode
= -- | always use SOCKS proxy when enabled = -- | always use SOCKS proxy when enabled
SMAlways SMAlways
@@ -257,7 +268,7 @@ instance StrEncoding SocksMode where
-- | network configuration for the client -- | network configuration for the client
data NetworkConfig = NetworkConfig data NetworkConfig = NetworkConfig
{ -- | use SOCKS5 proxy { -- | use SOCKS5 proxy
socksProxy :: Maybe SocksProxy, socksProxy :: Maybe SocksProxyWithAuth,
-- | when to use SOCKS proxy -- | when to use SOCKS proxy
socksMode :: SocksMode, socksMode :: SocksMode,
-- | determines critera which host is chosen from the list -- | determines critera which host is chosen from the list
@@ -355,12 +366,22 @@ transportClientConfig :: NetworkConfig -> TransportHost -> TransportClientConfig
transportClientConfig NetworkConfig {socksProxy, socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors} host = transportClientConfig NetworkConfig {socksProxy, socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors} host =
TransportClientConfig {socksProxy = useSocksProxy socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors, clientCredentials = Nothing, alpn = Nothing} TransportClientConfig {socksProxy = useSocksProxy socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors, clientCredentials = Nothing, alpn = Nothing}
where where
useSocksProxy SMAlways = socksProxy socksProxy' = (\(SocksProxyWithAuth _ proxy) -> proxy) <$> socksProxy
useSocksProxy SMAlways = socksProxy'
useSocksProxy SMOnion = case host of useSocksProxy SMOnion = case host of
THOnionHost _ -> socksProxy THOnionHost _ -> socksProxy'
_ -> Nothing _ -> Nothing
{-# INLINE transportClientConfig #-} {-# INLINE transportClientConfig #-}
clientSocksCredentials :: NetworkConfig -> ByteString -> Maybe SocksCredentials
clientSocksCredentials NetworkConfig {socksProxy} sessionUsername = case socksProxy of
Just (SocksProxyWithAuth auth _) -> case auth of
SocksAuthUsername {username, password} -> Just $ SocksCredentials username password
SocksAuthNull -> Nothing
SocksIsolateByAuth -> Just $ SocksCredentials sessionUsername ""
Nothing -> Nothing
{-# INLINE clientSocksCredentials #-}
-- | protocol client configuration. -- | protocol client configuration.
data ProtocolClientConfig v = ProtocolClientConfig data ProtocolClientConfig v = ProtocolClientConfig
{ -- | size of TBQueue to use for server commands and responses { -- | size of TBQueue to use for server commands and responses
@@ -489,9 +510,9 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize
runClient (port', ATransport t) useHost c = do runClient (port', ATransport t) useHost c = do
cVar <- newEmptyTMVarIO cVar <- newEmptyTMVarIO
let tcConfig = (transportClientConfig networkConfig useHost) {alpn = clientALPN} let tcConfig = (transportClientConfig networkConfig useHost) {alpn = clientALPN}
username = proxyUsername transportSession socksCreds = clientSocksCredentials networkConfig $ proxyUsername transportSession
tId <- tId <-
runTransportClient tcConfig (Just username) useHost port' (Just $ keyHash srv) (client t c cVar) runTransportClient tcConfig socksCreds useHost port' (Just $ keyHash srv) (client t c cVar)
`forkFinally` \_ -> void (atomically . tryPutTMVar cVar $ Left PCENetworkError) `forkFinally` \_ -> void (atomically . tryPutTMVar cVar $ Left PCENetworkError)
c_ <- tcpConnectTimeout `timeout` atomically (takeTMVar cVar) c_ <- tcpConnectTimeout `timeout` atomically (takeTMVar cVar)
case c_ of case c_ of
+2 -2
View File
@@ -4,7 +4,7 @@
module Simplex.Messaging.Crypto.SNTRUP761 where module Simplex.Messaging.Crypto.SNTRUP761 where
import Crypto.Hash (Digest, SHA3_256, hash) import Crypto.Hash (Digest, SHA256, hash)
import Data.ByteArray (ScrubbedBytes) import Data.ByteArray (ScrubbedBytes)
import qualified Data.ByteArray as BA import qualified Data.ByteArray as BA
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
@@ -28,4 +28,4 @@ kcbEncrypt (KEMHybridSecret k) = sbEncrypt_ k
kemHybridSecret :: PublicKeyX25519 -> PrivateKeyX25519 -> KEMSharedKey -> KEMHybridSecret kemHybridSecret :: PublicKeyX25519 -> PrivateKeyX25519 -> KEMSharedKey -> KEMHybridSecret
kemHybridSecret k pk (KEMSharedKey kem) = kemHybridSecret k pk (KEMSharedKey kem) =
let DhSecretX25519 dh = C.dh' k pk let DhSecretX25519 dh = C.dh' k pk
in KEMHybridSecret $ BA.convert (hash $ BA.convert dh <> kem :: Digest SHA3_256) in KEMHybridSecret $ BA.convert (hash $ BA.convert dh <> kem :: Digest SHA256)
@@ -443,6 +443,8 @@ data NtfSubStatus
NSInactive NSInactive
| -- | END received | -- | END received
NSEnd NSEnd
| -- | DELD received (connection was deleted)
NSDeleted
| -- | SMP AUTH error | -- | SMP AUTH error
NSAuth NSAuth
| -- | SMP error other than AUTH | -- | SMP error other than AUTH
@@ -456,6 +458,7 @@ ntfShouldSubscribe = \case
NSActive -> True NSActive -> True
NSInactive -> True NSInactive -> True
NSEnd -> False NSEnd -> False
NSDeleted -> False
NSAuth -> False NSAuth -> False
NSErr _ -> False NSErr _ -> False
@@ -466,6 +469,7 @@ instance Encoding NtfSubStatus where
NSActive -> "ACTIVE" NSActive -> "ACTIVE"
NSInactive -> "INACTIVE" NSInactive -> "INACTIVE"
NSEnd -> "END" NSEnd -> "END"
NSDeleted -> "DELETED"
NSAuth -> "AUTH" NSAuth -> "AUTH"
NSErr err -> "ERR " <> err NSErr err -> "ERR " <> err
smpP = smpP =
@@ -475,6 +479,7 @@ instance Encoding NtfSubStatus where
"ACTIVE" -> pure NSActive "ACTIVE" -> pure NSActive
"INACTIVE" -> pure NSInactive "INACTIVE" -> pure NSInactive
"END" -> pure NSEnd "END" -> pure NSEnd
"DELETED" -> pure NSDeleted
"AUTH" -> pure NSAuth "AUTH" -> pure NSAuth
"ERR" -> NSErr <$> (A.space *> A.takeByteString) "ERR" -> NSErr <$> (A.space *> A.takeByteString)
_ -> fail "bad NtfSubStatus" _ -> fail "bad NtfSubStatus"
@@ -226,6 +226,7 @@ ntfSubscriber NtfSubscriber {smpSubscribers, newSubQ, smpAgent = ca@SMPClientAge
Right SMP.END -> Right SMP.END ->
whenM (atomically $ activeClientSession' ca sessionId srv) $ whenM (atomically $ activeClientSession' ca sessionId srv) $
updateSubStatus smpQueue NSEnd updateSubStatus smpQueue NSEnd
Right SMP.DELD -> updateSubStatus smpQueue NSDeleted
Right (SMP.ERR e) -> logError $ "SMP server error: " <> tshow e Right (SMP.ERR e) -> logError $ "SMP server error: " <> tshow e
Right _ -> logError "SMP server unexpected response" Right _ -> logError "SMP server unexpected response"
Left e -> logError $ "SMP client error: " <> tshow e Left e -> logError $ "SMP client error: " <> tshow e
@@ -16,7 +16,7 @@ import qualified Data.Text as T
import qualified Data.Text.IO as T import qualified Data.Text.IO as T
import Network.Socket (HostName) import Network.Socket (HostName)
import Options.Applicative import Options.Applicative
import Simplex.Messaging.Client (NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig) import Simplex.Messaging.Client (HostMode (..), NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig, textToHostMode)
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig) import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Notifications.Server (runNtfServer) import Simplex.Messaging.Notifications.Server (runNtfServer)
@@ -92,6 +92,10 @@ ntfServerCLI cfgPath logPath =
<> "websockets: off\n\n\ <> "websockets: off\n\n\
\[SUBSCRIBER]\n\ \[SUBSCRIBER]\n\
\# Network configuration for notification server client.\n\ \# Network configuration for notification server client.\n\
\# `host_mode` can be 'public' (default) or 'onion'.\n\
\# It defines prefferred hostname for destination servers with multiple hostnames.\n\
\# host_mode: public\n\
\# required_host_mode: off\n\n\
\# SOCKS proxy port for subscribing to SMP servers.\n\ \# SOCKS proxy port for subscribing to SMP servers.\n\
\# You may need a separate instance of SOCKS proxy for incoming single-hop requests.\n\ \# You may need a separate instance of SOCKS proxy for incoming single-hop requests.\n\
\# socks_proxy: localhost:9050\n\n\ \# socks_proxy: localhost:9050\n\n\
@@ -134,6 +138,8 @@ ntfServerCLI cfgPath logPath =
defaultNetworkConfig defaultNetworkConfig
{ socksProxy = either error id <$!> strDecodeIni "SUBSCRIBER" "socks_proxy" ini, { socksProxy = either error id <$!> strDecodeIni "SUBSCRIBER" "socks_proxy" ini,
socksMode = maybe SMOnion (either error id) $! strDecodeIni "SUBSCRIBER" "socks_mode" ini, socksMode = maybe SMOnion (either error id) $! strDecodeIni "SUBSCRIBER" "socks_mode" ini,
hostMode = either (const HMPublic) (either error id . textToHostMode) $ lookupValue "SUBSCRIBER" "host_mode" ini,
requiredHostMode = fromMaybe False $ iniOnOff "SUBSCRIBER" "required_host_mode" ini,
smpPingInterval = 60_000_000 -- 1 minutes smpPingInterval = 60_000_000 -- 1 minutes
} }
}, },
+8
View File
@@ -484,6 +484,7 @@ data BrokerMsg where
RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy
PRES :: EncResponse -> BrokerMsg -- proxy to client PRES :: EncResponse -> BrokerMsg -- proxy to client
END :: BrokerMsg END :: BrokerMsg
DELD :: BrokerMsg
INFO :: QueueInfo -> BrokerMsg INFO :: QueueInfo -> BrokerMsg
OK :: BrokerMsg OK :: BrokerMsg
ERR :: ErrorType -> BrokerMsg ERR :: ErrorType -> BrokerMsg
@@ -705,6 +706,7 @@ data BrokerMsgTag
| RRES_ | RRES_
| PRES_ | PRES_
| END_ | END_
| DELD_
| INFO_ | INFO_
| OK_ | OK_
| ERR_ | ERR_
@@ -778,6 +780,7 @@ instance Encoding BrokerMsgTag where
RRES_ -> "RRES" RRES_ -> "RRES"
PRES_ -> "PRES" PRES_ -> "PRES"
END_ -> "END" END_ -> "END"
DELD_ -> "DELD"
INFO_ -> "INFO" INFO_ -> "INFO"
OK_ -> "OK" OK_ -> "OK"
ERR_ -> "ERR" ERR_ -> "ERR"
@@ -794,6 +797,7 @@ instance ProtocolMsgTag BrokerMsgTag where
"RRES" -> Just RRES_ "RRES" -> Just RRES_
"PRES" -> Just PRES_ "PRES" -> Just PRES_
"END" -> Just END_ "END" -> Just END_
"DELD" -> Just DELD_
"INFO" -> Just INFO_ "INFO" -> Just INFO_
"OK" -> Just OK_ "OK" -> Just OK_
"ERR" -> Just ERR_ "ERR" -> Just ERR_
@@ -1423,6 +1427,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock) RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock) PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
END -> e END_ END -> e END_
DELD
| v >= deletedEventSMPVersion -> e DELD_
| otherwise -> e END_
INFO info -> e (INFO_, ' ', info) INFO info -> e (INFO_, ' ', info)
OK -> e OK_ OK -> e OK_
ERR err -> e (ERR_, ' ', err) ERR err -> e (ERR_, ' ', err)
@@ -1448,6 +1455,7 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP) RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP) PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
END_ -> pure END END_ -> pure END
DELD_ -> pure DELD
INFO_ -> INFO <$> _smpP INFO_ -> INFO <$> _smpP
OK_ -> pure OK OK_ -> pure OK
ERR_ -> ERR <$> _smpP ERR_ -> ERR <$> _smpP
+47 -41
View File
@@ -136,9 +136,11 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
expired <- restoreServerMessages expired <- restoreServerMessages
restoreServerStats expired restoreServerStats expired
raceAny_ raceAny_
( serverThread s "server subscribedQ" subscribedQ subscribers pendingENDs subscriptions cancelSub ( serverThread s "server subscribedQ" True subscribedQ subscribers pendingENDs subscriptions cancelSub
: serverThread s "server ntfSubscribedQ" ntfSubscribedQ Env.notifiers pendingNtfENDs ntfSubscriptions (\_ -> pure ()) : serverThread s "server deletedQ" False deletedQ subscribers pendingDELDs subscriptions cancelSub
: sendPendingENDsThread s : serverThread s "server ntfSubscribedQ" True ntfSubscribedQ Env.notifiers pendingNtfENDs ntfSubscriptions (\_ -> pure ())
: serverThread s "server ntfDeletedQ" False ntfDeletedQ Env.notifiers pendingNtfDELDs ntfSubscriptions (\_ -> pure ())
: sendPendingEvtsThread s
: receiveFromProxyAgent pa : receiveFromProxyAgent pa
: map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg : map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg
) )
@@ -163,13 +165,14 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
forall s. forall s.
Server -> Server ->
String -> String ->
(Server -> TQueue (QueueId, ClientId, Subscribed)) -> Subscribed ->
(Server -> TQueue (QueueId, ClientId)) ->
(Server -> TMap QueueId (TVar Client)) -> (Server -> TMap QueueId (TVar Client)) ->
(Server -> TVar (IM.IntMap (NonEmpty RecipientId))) -> (Server -> TVar (IM.IntMap (NonEmpty RecipientId))) ->
(Client -> TMap QueueId s) -> (Client -> TMap QueueId s) ->
(s -> IO ()) -> (s -> IO ()) ->
M () M ()
serverThread s label subQ subs ends clientSubs unsub = do serverThread s label subscribed subQ subs pendingEvts clientSubs unsub = do
labelMyThread label labelMyThread label
cls <- asks clients cls <- asks clients
liftIO . forever $ liftIO . forever $
@@ -177,8 +180,8 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
$>>= endPreviousSubscriptions $>>= endPreviousSubscriptions
>>= mapM_ unsub >>= mapM_ unsub
where where
updateSubscribers :: TVar (IM.IntMap (Maybe Client)) -> (QueueId, ClientId, Bool) -> STM (Maybe (QueueId, Client)) updateSubscribers :: TVar (IM.IntMap (Maybe Client)) -> (QueueId, ClientId) -> STM (Maybe (QueueId, Client))
updateSubscribers cls (qId, clntId, subscribed) = updateSubscribers cls (qId, clntId) =
-- Client lookup by ID is in the same STM transaction. -- Client lookup by ID is in the same STM transaction.
-- In case client disconnects during the transaction, -- In case client disconnects during the transaction,
-- it will be re-evaluated, and the client won't be stored as subscribed. -- it will be re-evaluated, and the client won't be stored as subscribed.
@@ -201,37 +204,41 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
| otherwise = (\yes -> if yes then Just (qId, c') else Nothing) <$> readTVar (connected c') | otherwise = (\yes -> if yes then Just (qId, c') else Nothing) <$> readTVar (connected c')
endPreviousSubscriptions :: (QueueId, Client) -> IO (Maybe s) endPreviousSubscriptions :: (QueueId, Client) -> IO (Maybe s)
endPreviousSubscriptions (qId, c) = do endPreviousSubscriptions (qId, c) = do
atomically $ modifyTVar' (ends s) $ IM.alter (Just . maybe [qId] (qId <|)) (clientId c) atomically $ modifyTVar' (pendingEvts s) $ IM.alter (Just . maybe [qId] (qId <|)) (clientId c)
atomically $ TM.lookupDelete qId (clientSubs c) atomically $ TM.lookupDelete qId (clientSubs c)
sendPendingENDsThread :: Server -> M () sendPendingEvtsThread :: Server -> M ()
sendPendingENDsThread s = do sendPendingEvtsThread s = do
endInt <- asks $ pendingENDInterval . config endInt <- asks $ pendingENDInterval . config
cls <- asks clients cls <- asks clients
forever $ do forever $ do
threadDelay endInt threadDelay endInt
sendPending cls $ pendingENDs s sendPending cls END $ pendingENDs s
sendPending cls $ pendingNtfENDs s sendPending cls DELD $ pendingDELDs s
sendPending cls END $ pendingNtfENDs s
sendPending cls DELD $ pendingNtfDELDs s
where where
sendPending cls ref = do sendPending cls evt ref = do
ends <- atomically $ swapTVar ref IM.empty ends <- atomically $ swapTVar ref IM.empty
unless (null ends) $ forM_ (IM.assocs ends) $ \(cId, qIds) -> unless (null ends) $ forM_ (IM.assocs ends) $ \(cId, qIds) ->
mapM_ (queueENDs qIds) . join . IM.lookup cId =<< readTVarIO cls mapM_ (queueEvts qIds evt) . join . IM.lookup cId =<< readTVarIO cls
queueENDs qIds c@Client {connected, sndQ = q} = queueEvts qIds evt c@Client {connected, sndQ = q} =
whenM (readTVarIO connected) $ do whenM (readTVarIO connected) $ do
sent <- atomically $ ifM (isFullTBQueue q) (pure False) (writeTBQueue q ts $> True) sent <- atomically $ ifM (isFullTBQueue q) (pure False) (writeTBQueue q ts $> True)
if sent if sent
then updateEndStats then updateEndStats
else -- if queue is full it can block else -- if queue is full it can block
forkClient c ("sendPendingENDsThread.queueENDs") $ forkClient c ("sendPendingEvtsThread.queueEvts") $
atomically (writeTBQueue q ts) >> updateEndStats atomically (writeTBQueue q ts) >> updateEndStats
where where
ts = L.map (CorrId "",,END) qIds ts = L.map (CorrId "",,evt) qIds
updateEndStats = do updateEndStats = case evt of
stats <- asks serverStats END -> do
let len = L.length qIds stats <- asks serverStats
liftIO $ atomicModifyIORef'_ (qSubEnd stats) (+ len) let len = L.length qIds
liftIO $ atomicModifyIORef'_ (qSubEndB stats) (+ (len `div` 255 + 1)) -- up to 255 ENDs in the batch liftIO $ atomicModifyIORef'_ (qSubEnd stats) (+ len)
liftIO $ atomicModifyIORef'_ (qSubEndB stats) (+ (len `div` 255 + 1)) -- up to 255 ENDs in the batch
_ -> pure ()
receiveFromProxyAgent :: ProxyAgent -> M () receiveFromProxyAgent :: ProxyAgent -> M ()
receiveFromProxyAgent ProxyAgent {smpAgent = SMPClientAgent {agentQ}} = receiveFromProxyAgent ProxyAgent {smpAgent = SMPClientAgent {agentQ}} =
@@ -892,7 +899,7 @@ forkClient Client {endThreads, endThreadSeq} label action = do
mkWeakThreadId t >>= atomically . modifyTVar' endThreads . IM.insert tId mkWeakThreadId t >>= atomically . modifyTVar' endThreads . IM.insert tId
client :: THandleParams SMPVersion 'TServer -> Client -> Server -> M () client :: THandleParams SMPVersion 'TServer -> Client -> Server -> M ()
client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId, procThreads} Server {subscribedQ, ntfSubscribedQ, subscribers, notifiers} = do client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId, procThreads} Server {subscribedQ, deletedQ, ntfSubscribedQ, ntfDeletedQ, subscribers, notifiers} = do
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands" labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands"
forever $ forever $
atomically (readTBQueue rcvQ) atomically (readTBQueue rcvQ)
@@ -985,11 +992,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
processCommand (qr_, (corrId, entId, cmd)) = case cmd of processCommand (qr_, (corrId, entId, cmd)) = case cmd of
Cmd SProxiedClient command -> processProxiedCmd (corrId, entId, command) Cmd SProxiedClient command -> processProxiedCmd (corrId, entId, command)
Cmd SSender command -> Just <$> case command of Cmd SSender command -> Just <$> case command of
SKEY sKey -> (corrId,entId,) <$> case qr_ of SKEY sKey ->
Just qr@QueueRec {sndSecure} withQueue $ \QueueRec {sndSecure, recipientId} ->
| sndSecure -> secureQueue_ "SKEY" qr sKey (corrId,entId,) <$> if sndSecure then secureQueue_ "SKEY" recipientId sKey else pure $ ERR AUTH
| otherwise -> pure $ ERR AUTH
Nothing -> pure $ ERR INTERNAL
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
PING -> pure (corrId, NoEntity, PONG) PING -> pure (corrId, NoEntity, PONG)
RFWD encBlock -> (corrId, NoEntity,) <$> processForwardedCommand encBlock RFWD encBlock -> (corrId, NoEntity,) <$> processForwardedCommand encBlock
@@ -1009,9 +1014,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
SUB -> withQueue (`subscribeQueue` entId) SUB -> withQueue (`subscribeQueue` entId)
GET -> withQueue getMessage GET -> withQueue getMessage
ACK msgId -> withQueue (`acknowledgeMsg` msgId) ACK msgId -> withQueue (`acknowledgeMsg` msgId)
KEY sKey -> (corrId,entId,) <$> case qr_ of KEY sKey ->
Just qr -> secureQueue_ "KEY" qr sKey withQueue $ \QueueRec {recipientId} ->
Nothing -> pure $ ERR INTERNAL (corrId,entId,) <$> secureQueue_ "KEY" recipientId sKey
NKEY nKey dhKey -> addQueueNotifier_ st nKey dhKey NKEY nKey dhKey -> addQueueNotifier_ st nKey dhKey
NDEL -> deleteQueueNotifier_ st NDEL -> deleteQueueNotifier_ st
OFF -> suspendQueue_ st OFF -> suspendQueue_ st
@@ -1063,10 +1068,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
n <- asks $ queueIdBytes . config n <- asks $ queueIdBytes . config
liftM2 (,) (randomId n) (randomId n) liftM2 (,) (randomId n) (randomId n)
secureQueue_ :: T.Text -> QueueRec -> SndPublicAuthKey -> M BrokerMsg secureQueue_ :: T.Text -> RecipientId -> SndPublicAuthKey -> M BrokerMsg
secureQueue_ name qr@QueueRec {recipientId = rId} sKey = time name $ do secureQueue_ name rId sKey = time name $ do
withLog $ \s -> logSecureQueue s rId sKey withLog $ \s -> logSecureQueue s rId sKey
updateQueueDate qr
st <- asks queueStore st <- asks queueStore
stats <- asks serverStats stats <- asks serverStats
incStat $ qSecured stats incStat $ qSecured stats
@@ -1086,20 +1090,22 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
liftIO (addQueueNotifier st entId ntfCreds) >>= \case liftIO (addQueueNotifier st entId ntfCreds) >>= \case
Left DUPLICATE_ -> addNotifierRetry (n - 1) rcvPublicDhKey rcvNtfDhSecret Left DUPLICATE_ -> addNotifierRetry (n - 1) rcvPublicDhKey rcvNtfDhSecret
Left e -> pure $ ERR e Left e -> pure $ ERR e
Right _ -> do Right nId_ -> do
withLog $ \s -> logAddNotifier s entId ntfCreds withLog $ \s -> logAddNotifier s entId ntfCreds
incStat . ntfCreated =<< asks serverStats incStat . ntfCreated =<< asks serverStats
forM_ nId_ $ \nId -> atomically $ writeTQueue ntfDeletedQ (nId, clientId)
pure $ NID notifierId rcvPublicDhKey pure $ NID notifierId rcvPublicDhKey
deleteQueueNotifier_ :: QueueStore -> M (Transmission BrokerMsg) deleteQueueNotifier_ :: QueueStore -> M (Transmission BrokerMsg)
deleteQueueNotifier_ st = do deleteQueueNotifier_ st = do
withLog (`logDeleteNotifier` entId) withLog (`logDeleteNotifier` entId)
liftIO (deleteQueueNotifier st entId) >>= \case liftIO (deleteQueueNotifier st entId) >>= \case
Right () -> do Right (Just nId) -> do
-- Possibly, the same should be done if the queue is suspended, but currently we do not use it -- Possibly, the same should be done if the queue is suspended, but currently we do not use it
atomically $ writeTQueue ntfSubscribedQ (entId, clientId, False) atomically $ writeTQueue ntfDeletedQ (nId, clientId)
incStat . ntfDeleted =<< asks serverStats incStat . ntfDeleted =<< asks serverStats
pure ok pure ok
Right Nothing -> pure ok
Left e -> pure $ err e Left e -> pure $ err e
suspendQueue_ :: QueueStore -> M (Transmission BrokerMsg) suspendQueue_ :: QueueStore -> M (Transmission BrokerMsg)
@@ -1124,7 +1130,7 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
where where
newSub :: M Sub newSub :: M Sub
newSub = time "SUB newSub" . atomically $ do newSub = time "SUB newSub" . atomically $ do
writeTQueue subscribedQ (rId, clientId, True) writeTQueue subscribedQ (rId, clientId)
sub <- newSubscription NoSub sub <- newSubscription NoSub
TM.insert rId sub subscriptions TM.insert rId sub subscriptions
pure sub pure sub
@@ -1199,7 +1205,7 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
pure ok pure ok
where where
newSub = do newSub = do
writeTQueue ntfSubscribedQ (entId, clientId, True) writeTQueue ntfSubscribedQ (entId, clientId)
TM.insert entId () ntfSubscriptions TM.insert entId () ntfSubscriptions
acknowledgeMsg :: QueueRec -> MsgId -> M (Transmission BrokerMsg) acknowledgeMsg :: QueueRec -> MsgId -> M (Transmission BrokerMsg)
@@ -1480,9 +1486,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
liftIO (deleteQueue st entId $>>= \q -> delMsgQueue ms entId $> Right q) >>= \case liftIO (deleteQueue st entId $>>= \q -> delMsgQueue ms entId $> Right q) >>= \case
Right q -> do Right q -> do
-- Possibly, the same should be done if the queue is suspended, but currently we do not use it -- Possibly, the same should be done if the queue is suspended, but currently we do not use it
atomically $ writeTQueue subscribedQ (entId, clientId, False) atomically $ writeTQueue deletedQ (entId, clientId)
forM_ (notifierId <$> notifier q) $ \nId -> forM_ (notifierId <$> notifier q) $ \nId ->
atomically $ writeTQueue ntfSubscribedQ (nId, clientId, False) atomically $ writeTQueue ntfDeletedQ (nId, clientId)
updateDeletedStats q updateDeletedStats q
pure ok pure ok
Left e -> pure $ err e Left e -> pure $ err e
+11 -3
View File
@@ -136,12 +136,16 @@ data Env = Env
type Subscribed = Bool type Subscribed = Bool
data Server = Server data Server = Server
{ subscribedQ :: TQueue (RecipientId, ClientId, Subscribed), { subscribedQ :: TQueue (RecipientId, ClientId),
deletedQ :: TQueue (RecipientId, ClientId),
subscribers :: TMap RecipientId (TVar Client), subscribers :: TMap RecipientId (TVar Client),
ntfSubscribedQ :: TQueue (NotifierId, ClientId, Subscribed), ntfSubscribedQ :: TQueue (NotifierId, ClientId),
ntfDeletedQ :: TQueue (NotifierId, ClientId),
notifiers :: TMap NotifierId (TVar Client), notifiers :: TMap NotifierId (TVar Client),
pendingENDs :: TVar (IntMap (NonEmpty RecipientId)), pendingENDs :: TVar (IntMap (NonEmpty RecipientId)),
pendingDELDs :: TVar (IntMap (NonEmpty RecipientId)),
pendingNtfENDs :: TVar (IntMap (NonEmpty NotifierId)), pendingNtfENDs :: TVar (IntMap (NonEmpty NotifierId)),
pendingNtfDELDs :: TVar (IntMap (NonEmpty NotifierId)),
savingLock :: Lock savingLock :: Lock
} }
@@ -181,13 +185,17 @@ data Sub = Sub
newServer :: IO Server newServer :: IO Server
newServer = do newServer = do
subscribedQ <- newTQueueIO subscribedQ <- newTQueueIO
deletedQ <- newTQueueIO
subscribers <- TM.emptyIO subscribers <- TM.emptyIO
ntfSubscribedQ <- newTQueueIO ntfSubscribedQ <- newTQueueIO
ntfDeletedQ <- newTQueueIO
notifiers <- TM.emptyIO notifiers <- TM.emptyIO
pendingENDs <- newTVarIO IM.empty pendingENDs <- newTVarIO IM.empty
pendingDELDs <- newTVarIO IM.empty
pendingNtfENDs <- newTVarIO IM.empty pendingNtfENDs <- newTVarIO IM.empty
pendingNtfDELDs <- newTVarIO IM.empty
savingLock <- atomically createLock savingLock <- atomically createLock
return Server {subscribedQ, subscribers, ntfSubscribedQ, notifiers, pendingENDs, pendingNtfENDs, savingLock} return Server {subscribedQ, deletedQ, subscribers, ntfSubscribedQ, ntfDeletedQ, notifiers, pendingENDs, pendingDELDs, pendingNtfENDs, pendingNtfDELDs, savingLock}
newClient :: ClientId -> Natural -> VersionSMP -> ByteString -> SystemTime -> IO Client newClient :: ClientId -> Natural -> VersionSMP -> ByteString -> SystemTime -> IO Client
newClient clientId qSize thVersion sessionId createdAt = do newClient clientId qSize thVersion sessionId createdAt = do
+2 -7
View File
@@ -29,7 +29,7 @@ import qualified Data.Text.IO as T
import Network.Socket (HostName) import Network.Socket (HostName)
import Options.Applicative import Options.Applicative
import Simplex.Messaging.Agent.Protocol (connReqUriP') import Simplex.Messaging.Agent.Protocol (connReqUriP')
import Simplex.Messaging.Client (HostMode (..), NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig) import Simplex.Messaging.Client (HostMode (..), NetworkConfig (..), ProtocolClientConfig (..), SocksMode (..), defaultNetworkConfig, textToHostMode)
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig) import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
@@ -307,7 +307,7 @@ smpServerCLI_ generateSite serveStaticFiles cfgPath logPath =
defaultNetworkConfig defaultNetworkConfig
{ socksProxy = either error id <$!> strDecodeIni "PROXY" "socks_proxy" ini, { socksProxy = either error id <$!> strDecodeIni "PROXY" "socks_proxy" ini,
socksMode = maybe SMOnion (either error id) $! strDecodeIni "PROXY" "socks_mode" ini, socksMode = maybe SMOnion (either error id) $! strDecodeIni "PROXY" "socks_mode" ini,
hostMode = either (const HMPublic) textToHostMode $ lookupValue "PROXY" "host_mode" ini, hostMode = either (const HMPublic) (either error id . textToHostMode) $ lookupValue "PROXY" "host_mode" ini,
requiredHostMode = fromMaybe False $ iniOnOff "PROXY" "required_host_mode" ini requiredHostMode = fromMaybe False $ iniOnOff "PROXY" "required_host_mode" ini
} }
}, },
@@ -318,11 +318,6 @@ smpServerCLI_ generateSite serveStaticFiles cfgPath logPath =
serverClientConcurrency = readIniDefault defaultProxyClientConcurrency "PROXY" "client_concurrency" ini, serverClientConcurrency = readIniDefault defaultProxyClientConcurrency "PROXY" "client_concurrency" ini,
information = serverPublicInfo ini information = serverPublicInfo ini
} }
textToHostMode :: Text -> HostMode
textToHostMode = \case
"public" -> HMPublic
"onion" -> HMOnionViaSocks
s -> error . T.unpack $ "Invalid host_mode: " <> s
textToOwnServers :: Text -> [ByteString] textToOwnServers :: Text -> [ByteString]
textToOwnServers = map encodeUtf8 . T.words textToOwnServers = map encodeUtf8 . T.words
+11 -8
View File
@@ -74,23 +74,26 @@ secureQueue QueueStore {queues} rId sKey = toResult <$> do
let !q' = q {senderKey = Just sKey} let !q' = q {senderKey = Just sKey}
in writeTVar qVar q' $> Just q' in writeTVar qVar q' $> Just q'
addQueueNotifier :: QueueStore -> RecipientId -> NtfCreds -> IO (Either ErrorType QueueRec) addQueueNotifier :: QueueStore -> RecipientId -> NtfCreds -> IO (Either ErrorType (Maybe NotifierId))
addQueueNotifier QueueStore {queues, notifiers} rId ntfCreds@NtfCreds {notifierId = nId} = do addQueueNotifier QueueStore {queues, notifiers} rId ntfCreds@NtfCreds {notifierId = nId} = do
ifM (TM.memberIO nId notifiers) (pure $ Left DUPLICATE_) $ TM.lookupIO rId queues >>= \case
withQueue rId queues $ \qVar -> do Just qVar -> atomically $ ifM (TM.member nId notifiers) (pure $ Left DUPLICATE_) $ do
q <- readTVar qVar q <- readTVar qVar
forM_ (notifier q) $ (`TM.delete` notifiers) . notifierId nId_ <- forM (notifier q) $ \NtfCreds {notifierId} -> TM.delete notifierId notifiers $> notifierId
let !q' = q {notifier = Just ntfCreds} let !q' = q {notifier = Just ntfCreds}
writeTVar qVar q' writeTVar qVar q'
TM.insert nId rId notifiers TM.insert nId rId notifiers
pure q' pure $ Right nId_
Nothing -> pure $ Left AUTH
deleteQueueNotifier :: QueueStore -> RecipientId -> IO (Either ErrorType ()) deleteQueueNotifier :: QueueStore -> RecipientId -> IO (Either ErrorType (Maybe NotifierId))
deleteQueueNotifier QueueStore {queues, notifiers} rId = deleteQueueNotifier QueueStore {queues, notifiers} rId =
withQueue rId queues $ \qVar -> do withQueue rId queues $ \qVar -> do
q <- readTVar qVar q <- readTVar qVar
forM_ (notifier q) $ \NtfCreds {notifierId} -> TM.delete notifierId notifiers forM (notifier q) $ \NtfCreds {notifierId} -> do
writeTVar qVar $! q {notifier = Nothing} TM.delete notifierId notifiers
writeTVar qVar $! q {notifier = Nothing}
pure notifierId
suspendQueue :: QueueStore -> RecipientId -> IO (Either ErrorType ()) suspendQueue :: QueueStore -> RecipientId -> IO (Either ErrorType ())
suspendQueue QueueStore {queues} rId = suspendQueue QueueStore {queues} rId =
+9 -2
View File
@@ -47,6 +47,7 @@ module Simplex.Messaging.Transport
authCmdsSMPVersion, authCmdsSMPVersion,
sendingProxySMPVersion, sendingProxySMPVersion,
sndAuthKeySMPVersion, sndAuthKeySMPVersion,
deletedEventSMPVersion,
simplexMQVersion, simplexMQVersion,
smpBlockSize, smpBlockSize,
TransportConfig (..), TransportConfig (..),
@@ -130,6 +131,9 @@ smpBlockSize = 16384
-- 5 - basic auth for SMP servers (11/12/2022) -- 5 - basic auth for SMP servers (11/12/2022)
-- 6 - allow creating queues without subscribing (9/10/2023) -- 6 - allow creating queues without subscribing (9/10/2023)
-- 7 - support authenticated encryption to verify senders' commands, imply but do NOT send session ID in signed part (4/30/2024) -- 7 - support authenticated encryption to verify senders' commands, imply but do NOT send session ID in signed part (4/30/2024)
-- 8 - SMP proxy for sender commands
-- 9 - faster handshake: SKEY command for sender to secure queue
-- 10 - DELD event to subscriber when queue is deleted via another connnection
data SMPVersion data SMPVersion
@@ -160,14 +164,17 @@ sendingProxySMPVersion = VersionSMP 8
sndAuthKeySMPVersion :: VersionSMP sndAuthKeySMPVersion :: VersionSMP
sndAuthKeySMPVersion = VersionSMP 9 sndAuthKeySMPVersion = VersionSMP 9
deletedEventSMPVersion :: VersionSMP
deletedEventSMPVersion = VersionSMP 10
currentClientSMPRelayVersion :: VersionSMP currentClientSMPRelayVersion :: VersionSMP
currentClientSMPRelayVersion = VersionSMP 9 currentClientSMPRelayVersion = VersionSMP 10
legacyServerSMPRelayVersion :: VersionSMP legacyServerSMPRelayVersion :: VersionSMP
legacyServerSMPRelayVersion = VersionSMP 6 legacyServerSMPRelayVersion = VersionSMP 6
currentServerSMPRelayVersion :: VersionSMP currentServerSMPRelayVersion :: VersionSMP
currentServerSMPRelayVersion = VersionSMP 9 currentServerSMPRelayVersion = VersionSMP 10
-- Max SMP protocol version to be used in e2e encrypted -- Max SMP protocol version to be used in e2e encrypted
-- connection between client and server, as defined by SMP proxy. -- connection between client and server, as defined by SMP proxy.
+58 -21
View File
@@ -13,9 +13,13 @@ module Simplex.Messaging.Transport.Client
defaultSMPPort, defaultSMPPort,
defaultTcpConnectTimeout, defaultTcpConnectTimeout,
defaultTransportClientConfig, defaultTransportClientConfig,
defaultSocksProxyWithAuth,
defaultSocksProxy, defaultSocksProxy,
defaultSocksHost,
TransportClientConfig (..), TransportClientConfig (..),
SocksProxy, SocksProxy (..),
SocksProxyWithAuth (..),
SocksAuth (..),
TransportHost (..), TransportHost (..),
TransportHosts (..), TransportHosts (..),
TransportHosts_ (..), TransportHosts_ (..),
@@ -23,7 +27,7 @@ module Simplex.Messaging.Transport.Client
) )
where where
import Control.Applicative (optional) import Control.Applicative (optional, (<|>))
import Control.Logger.Simple (logError) import Control.Logger.Simple (logError)
import Control.Monad (when) import Control.Monad (when)
import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.Aeson (FromJSON (..), ToJSON (..))
@@ -79,7 +83,7 @@ instance StrEncoding TransportHost where
strP = strP =
A.choice A.choice
[ THIPv4 <$> ((,,,) <$> ipNum <*> ipNum <*> ipNum <*> A.decimal), [ THIPv4 <$> ((,,,) <$> ipNum <*> ipNum <*> ipNum <*> A.decimal),
maybe (Left "bad IPv6") (Right . THIPv6 . fromIPv6w) . readMaybe . B.unpack <$?> A.takeWhile1 (\c -> isHexDigit c || c == ':'), maybe (Left "bad IPv6") (Right . THIPv6 . fromIPv6w) . readMaybe . B.unpack <$?> ipv6StrP,
THOnionHost <$> ((<>) <$> A.takeWhile (\c -> isAsciiLower c || isDigit c) <*> A.string ".onion"), THOnionHost <$> ((<>) <$> A.takeWhile (\c -> isAsciiLower c || isDigit c) <*> A.string ".onion"),
THDomainName . B.unpack <$> (notOnion <$?> A.takeWhile1 (A.notInClass ":#,;/ \n\r\t")) THDomainName . B.unpack <$> (notOnion <$?> A.takeWhile1 (A.notInClass ":#,;/ \n\r\t"))
] ]
@@ -87,6 +91,9 @@ instance StrEncoding TransportHost where
ipNum = validIP <$?> (A.decimal <* A.char '.') ipNum = validIP <$?> (A.decimal <* A.char '.')
validIP :: Int -> Either String Word8 validIP :: Int -> Either String Word8
validIP n = if 0 <= n && n <= 255 then Right $ fromIntegral n else Left "invalid IP address" validIP n = if 0 <= n && n <= 255 then Right $ fromIntegral n else Left "invalid IP address"
ipv6StrP =
A.char '[' *> A.takeWhile1 (/= ']') <* A.char ']'
<|> A.takeWhile1 (\c -> isHexDigit c || c == ':')
notOnion s = if ".onion" `B.isSuffixOf` s then Left "invalid onion host" else Right s notOnion s = if ".onion" `B.isSuffixOf` s then Left "invalid onion host" else Right s
instance ToJSON TransportHost where instance ToJSON TransportHost where
@@ -134,16 +141,16 @@ clientTransportConfig TransportClientConfig {logTLSErrors} =
TransportConfig {logTLSErrors, transportTimeout = Nothing} TransportConfig {logTLSErrors, transportTimeout = Nothing}
-- | Connect to passed TCP host:port and pass handle to the client. -- | Connect to passed TCP host:port and pass handle to the client.
runTransportClient :: Transport c => TransportClientConfig -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a runTransportClient :: Transport c => TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
runTransportClient = runTLSTransportClient supportedParameters Nothing runTransportClient = runTLSTransportClient supportedParameters Nothing
runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (c -> IO a) -> IO a
runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, tcpKeepAlive, clientCredentials, alpn} proxyUsername host port keyHash client = do runTLSTransportClient tlsParams caStore_ cfg@TransportClientConfig {socksProxy, tcpKeepAlive, clientCredentials, alpn} socksCreds host port keyHash client = do
serverCert <- newEmptyTMVarIO serverCert <- newEmptyTMVarIO
let hostName = B.unpack $ strEncode host let hostName = B.unpack $ strEncode host
clientParams = mkTLSClientParams tlsParams caStore_ hostName port keyHash clientCredentials alpn serverCert clientParams = mkTLSClientParams tlsParams caStore_ hostName port keyHash clientCredentials alpn serverCert
connectTCP = case socksProxy of connectTCP = case socksProxy of
Just proxy -> connectSocksClient proxy proxyUsername (hostAddr host) Just proxy -> connectSocksClient proxy socksCreds (hostAddr host)
_ -> connectTCPClient hostName _ -> connectTCPClient hostName
c <- do c <- do
sock <- connectTCP port sock <- connectTCP port
@@ -191,40 +198,70 @@ connectTCPClient host port = withSocketsDo $ resolve >>= tryOpen err
defaultSMPPort :: PortNumber defaultSMPPort :: PortNumber
defaultSMPPort = 5223 defaultSMPPort = 5223
connectSocksClient :: SocksProxy -> Maybe ByteString -> SocksHostAddress -> ServiceName -> IO Socket connectSocksClient :: SocksProxy -> Maybe SocksCredentials -> SocksHostAddress -> ServiceName -> IO Socket
connectSocksClient (SocksProxy addr) proxyUsername hostAddr _port = do connectSocksClient (SocksProxy addr) socksCreds hostAddr _port = do
let port = if null _port then defaultSMPPort else fromMaybe defaultSMPPort $ readMaybe _port let port = if null _port then defaultSMPPort else fromMaybe defaultSMPPort $ readMaybe _port
fst <$> case proxyUsername of fst <$> case socksCreds of
Just username -> socksConnectAuth (defaultSocksConf addr) (SocksAddress hostAddr port) (SocksCredentials username "") Just creds -> socksConnectAuth (defaultSocksConf addr) (SocksAddress hostAddr port) creds
_ -> socksConnect (defaultSocksConf addr) (SocksAddress hostAddr port) _ -> socksConnect (defaultSocksConf addr) (SocksAddress hostAddr port)
defaultSocksHost :: HostAddress defaultSocksHost :: (Word8, Word8, Word8, Word8)
defaultSocksHost = tupleToHostAddress (127, 0, 0, 1) defaultSocksHost = (127, 0, 0, 1)
defaultSocksProxyWithAuth :: SocksProxyWithAuth
defaultSocksProxyWithAuth = SocksProxyWithAuth SocksIsolateByAuth defaultSocksProxy
defaultSocksProxy :: SocksProxy defaultSocksProxy :: SocksProxy
defaultSocksProxy = SocksProxy $ SockAddrInet 9050 defaultSocksHost defaultSocksProxy = SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress defaultSocksHost
newtype SocksProxy = SocksProxy SockAddr newtype SocksProxy = SocksProxy SockAddr
deriving (Eq) deriving (Eq)
data SocksProxyWithAuth = SocksProxyWithAuth SocksAuth SocksProxy
deriving (Eq, Show)
data SocksAuth
= SocksAuthUsername {username :: ByteString, password :: ByteString}
| SocksAuthNull
| SocksIsolateByAuth -- this is default
deriving (Eq, Show)
instance Show SocksProxy where show (SocksProxy addr) = show addr instance Show SocksProxy where show (SocksProxy addr) = show addr
instance StrEncoding SocksProxy where instance StrEncoding SocksProxy where
strEncode = B.pack . show strEncode = B.pack . show
strP = do strP = do
host <- maybe defaultSocksHost tupleToHostAddress <$> optional ipv4P host <- fromMaybe (THIPv4 defaultSocksHost) <$> optional strP
port <- fromMaybe 9050 <$> optional (A.char ':' *> (fromInteger <$> A.decimal)) port <- fromMaybe 9050 <$> optional (A.char ':' *> (fromInteger <$> A.decimal))
pure . SocksProxy $ SockAddrInet port host SocksProxy <$> socksAddr port host
where where
ipv4P = (,,,) <$> ipNum <*> ipNum <*> ipNum <*> A.decimal socksAddr port = \case
ipNum = A.decimal <* A.char '.' THIPv4 addr -> pure $ SockAddrInet port $ tupleToHostAddress addr
THIPv6 addr -> pure $ SockAddrInet6 port 0 addr 0
_ -> fail "SOCKS5 host should be IPv4 or IPv6 address"
instance ToJSON SocksProxy where instance StrEncoding SocksProxyWithAuth where
strEncode (SocksProxyWithAuth auth proxy) = strEncode auth <> strEncode proxy
strP = SocksProxyWithAuth <$> strP <*> strP
instance ToJSON SocksProxyWithAuth where
toJSON = strToJSON toJSON = strToJSON
toEncoding = strToJEncoding toEncoding = strToJEncoding
instance FromJSON SocksProxy where instance FromJSON SocksProxyWithAuth where
parseJSON = strParseJSON "SocksProxy" parseJSON = strParseJSON "SocksProxyWithAuth"
instance StrEncoding SocksAuth where
strEncode = \case
SocksAuthUsername {username, password} -> username <> ":" <> password <> "@"
SocksAuthNull -> "@"
SocksIsolateByAuth -> ""
strP = usernameP <|> (SocksAuthNull <$ A.char '@') <|> pure SocksIsolateByAuth
where
usernameP = do
username <- A.takeTill (== ':') <* A.char ':'
password <- A.takeTill (== '@') <* A.char '@'
pure SocksAuthUsername {username, password}
mkTLSClientParams :: T.Supported -> Maybe XS.CertificateStore -> HostName -> ServiceName -> Maybe C.KeyHash -> Maybe (X.CertificateChain, T.PrivKey) -> Maybe [ALPN] -> TMVar X.CertificateChain -> T.ClientParams mkTLSClientParams :: T.Supported -> Maybe XS.CertificateStore -> HostName -> ServiceName -> Maybe C.KeyHash -> Maybe (X.CertificateChain, T.PrivKey) -> Maybe [ALPN] -> TMVar X.CertificateChain -> T.ClientParams
mkTLSClientParams supported caStore_ host port cafp_ clientCreds_ alpn_ serverCerts = mkTLSClientParams supported caStore_ host port cafp_ clientCreds_ alpn_ serverCerts =
@@ -11,7 +11,6 @@ import Control.Concurrent.Async
import Control.Exception (IOException, try) import Control.Exception (IOException, try)
import qualified Control.Exception as E import qualified Control.Exception as E
import Control.Monad import Control.Monad
import Data.ByteString.Char8 (ByteString)
import Data.Functor (($>)) import Data.Functor (($>))
import Data.Time (UTCTime, getCurrentTime) import Data.Time (UTCTime, getCurrentTime)
import qualified Data.X509 as X import qualified Data.X509 as X
@@ -20,6 +19,7 @@ import Network.HPACK (BufferSize)
import Network.HTTP2.Client (ClientConfig (..), Request, Response) import Network.HTTP2.Client (ClientConfig (..), Request, Response)
import qualified Network.HTTP2.Client as H import qualified Network.HTTP2.Client as H
import Network.Socket (HostName, ServiceName) import Network.Socket (HostName, ServiceName)
import Network.Socks5 (SocksCredentials)
import qualified Network.TLS as T import qualified Network.TLS as T
import Numeric.Natural (Natural) import Numeric.Natural (Natural)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
@@ -91,10 +91,10 @@ data HTTP2ClientError = HCResponseTimeout | HCNetworkError | HCIOError IOExcepti
getHTTP2Client :: HostName -> ServiceName -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client) getHTTP2Client :: HostName -> ServiceName -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client)
getHTTP2Client host port = getVerifiedHTTP2Client Nothing (THDomainName host) port Nothing getHTTP2Client host port = getVerifiedHTTP2Client Nothing (THDomainName host) port Nothing
getVerifiedHTTP2Client :: Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client) getVerifiedHTTP2Client :: Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client)
getVerifiedHTTP2Client proxyUsername host port keyHash caStore config disconnected = getVerifiedHTTP2ClientWith config host port disconnected setup getVerifiedHTTP2Client socksCreds host port keyHash caStore config disconnected = getVerifiedHTTP2ClientWith config host port disconnected setup
where where
setup = runHTTP2Client (suportedTLSParams config) caStore (transportConfig config) (bufferSize config) proxyUsername host port keyHash setup = runHTTP2Client (suportedTLSParams config) caStore (transportConfig config) (bufferSize config) socksCreds host port keyHash
attachHTTP2Client :: HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> Int -> TLS -> IO (Either HTTP2ClientError HTTP2Client) attachHTTP2Client :: HTTP2ClientConfig -> TransportHost -> ServiceName -> IO () -> Int -> TLS -> IO (Either HTTP2ClientError HTTP2Client)
attachHTTP2Client config host port disconnected bufferSize tls = getVerifiedHTTP2ClientWith config host port disconnected setup attachHTTP2Client config host port disconnected bufferSize tls = getVerifiedHTTP2ClientWith config host port disconnected setup
@@ -178,11 +178,11 @@ sendRequestDirect HTTP2Client {client_ = HClient {config, disconnected}, sendReq
http2RequestTimeout :: HTTP2ClientConfig -> Maybe Int -> Int http2RequestTimeout :: HTTP2ClientConfig -> Maybe Int -> Int
http2RequestTimeout HTTP2ClientConfig {connTimeout} = maybe connTimeout (connTimeout +) http2RequestTimeout HTTP2ClientConfig {connTimeout} = maybe connTimeout (connTimeout +)
runHTTP2Client :: forall a. T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> BufferSize -> Maybe ByteString -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (TLS -> H.Client a) -> IO a runHTTP2Client :: forall a. T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> BufferSize -> Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> (TLS -> H.Client a) -> IO a
runHTTP2Client tlsParams caStore tcConfig bufferSize proxyUsername host port keyHash = runHTTP2ClientWith bufferSize host setup runHTTP2Client tlsParams caStore tcConfig bufferSize socksCreds host port keyHash = runHTTP2ClientWith bufferSize host setup
where where
setup :: (TLS -> IO a) -> IO a setup :: (TLS -> IO a) -> IO a
setup = runTLSTransportClient tlsParams caStore tcConfig proxyUsername host port keyHash setup = runTLSTransportClient tlsParams caStore tcConfig socksCreds host port keyHash
runHTTP2ClientWith :: forall a. BufferSize -> TransportHost -> ((TLS -> IO a) -> IO a) -> (TLS -> H.Client a) -> IO a runHTTP2ClientWith :: forall a. BufferSize -> TransportHost -> ((TLS -> IO a) -> IO a) -> (TLS -> H.Client a) -> IO a
runHTTP2ClientWith bufferSize host setup client = setup $ \tls -> withHTTP2 bufferSize (run tls) (pure ()) tls runHTTP2ClientWith bufferSize host setup client = setup $ \tls -> withHTTP2 bufferSize (run tls) (pure ()) tls
+1 -1
View File
@@ -166,7 +166,7 @@ testNtfMatrix t runTest = do
it "curr servers; curr clients" $ runNtfTestCfg t 1 cfg ntfServerCfg agentCfg agentCfg runTest it "curr servers; curr clients" $ runNtfTestCfg t 1 cfg ntfServerCfg agentCfg agentCfg runTest
it "curr servers; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest it "curr servers; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest
it "prev servers; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest it "prev servers; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest
it "prev servers; curr clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfgVPrev agentCfg agentCfg runTest it "prev servers; curr clients" $ runNtfTestCfg t 1 cfgVPrev ntfServerCfgVPrev agentCfg agentCfg runTest
-- servers can be upgraded in any order -- servers can be upgraded in any order
it "servers: curr SMP, prev NTF; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest it "servers: curr SMP, prev NTF; prev clients" $ runNtfTestCfg t 3 cfg ntfServerCfgVPrev agentCfgVPrevPQ agentCfgVPrevPQ runTest
it "servers: prev SMP, curr NTF; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest it "servers: prev SMP, curr NTF; prev clients" $ runNtfTestCfg t 3 cfgVPrev ntfServerCfg agentCfgVPrevPQ agentCfgVPrevPQ runTest
+127
View File
@@ -0,0 +1,127 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
module CoreTests.SOCKSSettings where
import Network.Socket (SockAddr (..), tupleToHostAddress)
import Simplex.Messaging.Client
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (ErrorType)
import Simplex.Messaging.Transport.Client
import Test.Hspec
socksSettingsTests :: Spec
socksSettingsTests = do
describe "hostMode and requiredHostMode settings" testHostMode
describe "socksMode setting, independent of hostMode setting" testSocksMode
describe "socks proxy address encoding" testSocksProxyEncoding
testPublicHost :: TransportHost
testPublicHost = "smp.example.com"
testOnionHost :: TransportHost
testOnionHost = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrst.onion"
testHostMode :: Spec
testHostMode = do
describe "requiredHostMode = False (default)" $ do
it "without socks proxy, should choose onion host only with HMOnion" $ do
chooseTransportHost @ErrorType defaultNetworkConfig [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
chooseHost HMOnionViaSocks Nothing [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
chooseHost HMOnion Nothing [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
chooseHost HMPublic Nothing [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
it "with socks proxy, should choose onion host with HMOnionViaSocks (default) and HMOnion" $ do
chooseTransportHost @ErrorType defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
chooseHost HMOnionViaSocks (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
chooseHost HMOnion (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testOnionHost
chooseHost HMPublic (Just defaultSocksProxyWithAuth) [testPublicHost, testOnionHost] `shouldBe` Right testPublicHost
it "should choose any available host, if preferred not available" $ do
chooseHost HMOnionViaSocks Nothing [testOnionHost] `shouldBe` Right testOnionHost
chooseHost HMOnion Nothing [testPublicHost] `shouldBe` Right testPublicHost
chooseHost HMPublic Nothing [testOnionHost] `shouldBe` Right testOnionHost
chooseHost HMOnionViaSocks (Just defaultSocksProxyWithAuth) [testPublicHost] `shouldBe` Right testPublicHost
chooseHost HMOnion (Just defaultSocksProxyWithAuth) [testPublicHost] `shouldBe` Right testPublicHost
chooseHost HMPublic (Just defaultSocksProxyWithAuth) [testOnionHost] `shouldBe` Right testOnionHost
describe "requiredHostMode = True" $ do
it "should fail, if preferred host not available" $ do
testOnionHost `incompatible` (HMOnionViaSocks, Nothing)
testPublicHost `incompatible` (HMOnion, Nothing)
testOnionHost `incompatible` (HMPublic, Nothing)
testPublicHost `incompatible` (HMOnionViaSocks, Just defaultSocksProxyWithAuth)
testPublicHost `incompatible` (HMOnion, Just defaultSocksProxyWithAuth)
testOnionHost `incompatible` (HMPublic, Just defaultSocksProxyWithAuth)
it "should choose preferred host, if available" $ do
testPublicHost `compatible` (HMOnionViaSocks, Nothing)
testOnionHost `compatible` (HMOnion, Nothing)
testPublicHost `compatible` (HMPublic, Nothing)
testOnionHost `compatible` (HMOnionViaSocks, Just defaultSocksProxyWithAuth)
testOnionHost `compatible` (HMOnion, Just defaultSocksProxyWithAuth)
testPublicHost `compatible` (HMPublic, Just defaultSocksProxyWithAuth)
where
chooseHost = chooseHostCfg defaultNetworkConfig
host `incompatible` (hostMode, socksProxy) =
chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host] `shouldBe` Left PCEIncompatibleHost
host `compatible` (hostMode, socksProxy) = do
chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host] `shouldBe` Right host
chooseHostCfg defaultNetworkConfig {requiredHostMode = True} hostMode socksProxy [host, testPublicHost, testOnionHost] `shouldBe` Right host
chooseHostCfg cfg hostMode socksProxy =
chooseTransportHost @ErrorType cfg {hostMode, socksProxy}
testSocksMode :: Spec
testSocksMode = do
it "should not use SOCKS proxy if not specified" $ do
transportSocksCfg defaultNetworkConfig testPublicHost `shouldBe` Nothing
transportSocksCfg defaultNetworkConfig testOnionHost `shouldBe` Nothing
transportSocks Nothing SMAlways testPublicHost `shouldBe` Nothing
transportSocks Nothing SMAlways testOnionHost `shouldBe` Nothing
transportSocks Nothing SMOnion testPublicHost `shouldBe` Nothing
transportSocks Nothing SMOnion testOnionHost `shouldBe` Nothing
it "should always use SOCKS proxy if specified and (socksMode = SMAlways or (socksMode = SMOnion and onion host))" $ do
transportSocksCfg defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} testPublicHost `shouldBe` Just defaultSocksProxy
transportSocksCfg defaultNetworkConfig {socksProxy = Just defaultSocksProxyWithAuth} testOnionHost `shouldBe` Just defaultSocksProxy
transportSocks (Just defaultSocksProxyWithAuth) SMAlways testPublicHost `shouldBe` Just defaultSocksProxy
transportSocks (Just defaultSocksProxyWithAuth) SMAlways testOnionHost `shouldBe` Just defaultSocksProxy
transportSocks (Just defaultSocksProxyWithAuth) SMOnion testPublicHost `shouldBe` Nothing
transportSocks (Just defaultSocksProxyWithAuth) SMOnion testOnionHost `shouldBe` Just defaultSocksProxy
where
transportSocks proxy socksMode = transportSocksCfg defaultNetworkConfig {socksProxy = proxy, socksMode}
transportSocksCfg cfg host =
let TransportClientConfig {socksProxy} = transportClientConfig cfg host
in socksProxy
testSocksProxyEncoding :: Spec
testSocksProxyEncoding = do
it "should decode SOCKS proxy with isolate-by-auth mode" $ do
let authIsolate proxy = Right $ SocksProxyWithAuth SocksIsolateByAuth proxy
strDecode "" `shouldBe` authIsolate defaultSocksProxy
strDecode ":9050" `shouldBe` authIsolate defaultSocksProxy
strDecode ":8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 8080 $ tupleToHostAddress defaultSocksHost)
strDecode "127.0.0.1" `shouldBe` authIsolate defaultSocksProxy
strDecode "1.1.1.1" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress (1, 1, 1, 1))
strDecode "::1" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)
strDecode "[fd12:3456:789a:1::1]" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0xfd123456, 0x789a0001, 0, 1) 0)
strDecode "127.0.0.1:9050" `shouldBe` authIsolate defaultSocksProxy
strDecode "127.0.0.1:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet 8080 $ tupleToHostAddress defaultSocksHost)
strDecode "[::1]:9050" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 9050 0 (0, 0, 0, 1) 0)
strDecode "[::1]:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 8080 0 (0, 0, 0, 1) 0)
strDecode "[fd12:3456:789a:1::1]:8080" `shouldBe` authIsolate (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
it "should decode SOCKS proxy without credentials" $ do
let authNull proxy = Right $ SocksProxyWithAuth SocksAuthNull proxy
strDecode "@" `shouldBe` authNull defaultSocksProxy
strDecode "@:9050" `shouldBe` authNull defaultSocksProxy
strDecode "@127.0.0.1" `shouldBe` authNull defaultSocksProxy
strDecode "@1.1.1.1" `shouldBe` authNull (SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress (1, 1, 1, 1))
strDecode "@127.0.0.1:9050" `shouldBe` authNull defaultSocksProxy
strDecode "@[fd12:3456:789a:1::1]:8080" `shouldBe` authNull (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
it "should decode SOCKS proxy with credentials" $ do
let authUser proxy = Right $ SocksProxyWithAuth SocksAuthUsername {username = "user", password = "pass"} proxy
strDecode "user:pass@" `shouldBe` authUser defaultSocksProxy
strDecode "user:pass@:9050" `shouldBe` authUser defaultSocksProxy
strDecode "user:pass@127.0.0.1" `shouldBe` authUser defaultSocksProxy
strDecode "user:pass@127.0.0.1:9050" `shouldBe` authUser defaultSocksProxy
strDecode "user:pass@fd12:3456:789a:1::1" `shouldBe` authUser (SocksProxy $ SockAddrInet6 9050 0 (0xfd123456, 0x789a0001, 0, 1) 0)
strDecode "user:pass@[fd12:3456:789a:1::1]:8080" `shouldBe` authUser (SocksProxy $ SockAddrInet6 8080 0 (0xfd123456, 0x789a0001, 0, 1) 0)
+8 -1
View File
@@ -385,6 +385,10 @@ testSwitchSub (ATransport t) =
Resp "bcda" _ ok3 <- signSendRecv rh2 rKey ("bcda", rId, ACK mId3) Resp "bcda" _ ok3 <- signSendRecv rh2 rKey ("bcda", rId, ACK mId3)
(ok3, OK) #== "accepts ACK from the 2nd TCP connection" (ok3, OK) #== "accepts ACK from the 2nd TCP connection"
Resp "cdab" _ OK <- signSendRecv rh1 rKey ("cdab", rId, DEL)
Resp "" rId' DELD <- tGet1 rh2
(rId', rId) #== "connection deleted event delivered to subscribed client"
1000 `timeout` tGet @SMPVersion @ErrorType @BrokerMsg rh1 >>= \case 1000 `timeout` tGet @SMPVersion @ErrorType @BrokerMsg rh1 >>= \case
Nothing -> return () Nothing -> return ()
Just _ -> error "nothing else is delivered to the 1st TCP connection" Just _ -> error "nothing else is delivered to the 1st TCP connection"
@@ -839,7 +843,8 @@ testMessageNotifications (ATransport t) =
Resp "3a" _ OK <- signSendRecv rh rKey ("3a", rId, ACK mId1) Resp "3a" _ OK <- signSendRecv rh rKey ("3a", rId, ACK mId1)
Resp "" _ (NMSG _ _) <- tGet1 nh1 Resp "" _ (NMSG _ _) <- tGet1 nh1
Resp "4" _ OK <- signSendRecv nh2 nKey ("4", nId, NSUB) Resp "4" _ OK <- signSendRecv nh2 nKey ("4", nId, NSUB)
Resp "" _ END <- tGet1 nh1 Resp "" nId2 END <- tGet1 nh1
nId2 `shouldBe` nId
Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, _SEND' "hello again") Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, _SEND' "hello again")
Resp "" _ (Msg mId2 msg2) <- tGet1 rh Resp "" _ (Msg mId2 msg2) <- tGet1 rh
Resp "5a" _ OK <- signSendRecv rh rKey ("5a", rId, ACK mId2) Resp "5a" _ OK <- signSendRecv rh rKey ("5a", rId, ACK mId2)
@@ -849,6 +854,8 @@ testMessageNotifications (ATransport t) =
Nothing -> pure () Nothing -> pure ()
Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection" Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection"
Resp "6" _ OK <- signSendRecv rh rKey ("6", rId, NDEL) Resp "6" _ OK <- signSendRecv rh rKey ("6", rId, NDEL)
Resp "" nId3 DELD <- tGet1 nh2
nId3 `shouldBe` nId
Resp "7" _ OK <- signSendRecv sh sKey ("7", sId, _SEND' "hello there") Resp "7" _ OK <- signSendRecv sh sKey ("7", sId, _SEND' "hello there")
Resp "" _ (Msg mId3 msg3) <- tGet1 rh Resp "" _ (Msg mId3 msg3) <- tGet1 rh
(dec mId3 msg3, Right "hello there") #== "delivered from queue again" (dec mId3 msg3, Right "hello there") #== "delivered from queue again"
+2
View File
@@ -12,6 +12,7 @@ import CoreTests.CryptoFileTests
import CoreTests.CryptoTests import CoreTests.CryptoTests
import CoreTests.EncodingTests import CoreTests.EncodingTests
import CoreTests.RetryIntervalTests import CoreTests.RetryIntervalTests
import CoreTests.SOCKSSettings
import CoreTests.TRcvQueuesTests import CoreTests.TRcvQueuesTests
import CoreTests.UtilTests import CoreTests.UtilTests
import CoreTests.VersionRangeTests import CoreTests.VersionRangeTests
@@ -52,6 +53,7 @@ main = do
describe "Encryption tests" cryptoTests describe "Encryption tests" cryptoTests
describe "Encrypted files tests" cryptoFileTests describe "Encrypted files tests" cryptoFileTests
describe "Retry interval tests" retryIntervalTests describe "Retry interval tests" retryIntervalTests
describe "SOCKS settings tests" socksSettingsTests
describe "TRcvQueues tests" tRcvQueuesTests describe "TRcvQueues tests" tRcvQueuesTests
describe "Util tests" utilTests describe "Util tests" utilTests
describe "SMP server via TLS" $ serverTests (transport @TLS) describe "SMP server via TLS" $ serverTests (transport @TLS)
+2 -2
View File
@@ -429,7 +429,7 @@ testXFTPAgentSendRestore = withGlobalLogging logCfgNoLogs $ do
("", sfId', SFPROG _ _) <- sfGet sndr' ("", sfId', SFPROG _ _) <- sfGet sndr'
liftIO $ sfId' `shouldBe` sfId liftIO $ sfId' `shouldBe` sfId
threadDelay 100000 threadDelay 200000
withXFTPServerStoreLogOn $ \_ -> do withXFTPServerStoreLogOn $ \_ -> do
-- send file - should continue uploading with server up -- send file - should continue uploading with server up
@@ -443,7 +443,7 @@ testXFTPAgentSendRestore = withGlobalLogging logCfgNoLogs $ do
pure rfd1 pure rfd1
-- prefix path should be removed after sending file -- prefix path should be removed after sending file
threadDelay 100000 threadDelay 200000
doesDirectoryExist prefixPath `shouldReturn` False doesDirectoryExist prefixPath `shouldReturn` False
doesFileExist encPath `shouldReturn` False doesFileExist encPath `shouldReturn` False