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
Agent:
+1 -1
View File
@@ -1,5 +1,5 @@
name: simplexmq
version: 6.0.3.0
version: 6.0.5.0
synopsis: SimpleXMQ message broker
description: |
This package includes <./docs/Simplex-Messaging-Server.html server>,
+2 -2
View File
@@ -250,7 +250,7 @@ In pseudo-code:
```
// session 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))
kemCiphertext(1) = enc(kemSecret(1), kemEncKey(1))
// kemEncKey is included in host HELLO, kemCiphertext - in controller HELLO
@@ -262,7 +262,7 @@ dhSecret(n') = dh(hostHelloDhKey(n - 1), controllerDhKey(n))
// session 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))
// controllerDhKey(n) is either from invitation or from multicast announcement
kemCiphertext(n) = enc(kemSecret(n), kemEncKey(n))
+165 -21
View File
@@ -1,30 +1,174 @@
#!/usr/bin/env sh
set -eu
path_conf_var="/var/opt"
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"
# Common
# ------
date="$(date -u '+%Y-%m-%dT%H:%M:%S')"
backup_smp() {
if [ -e "$path_conf_storelog_smp" ]; then
cp "$path_conf_storelog_smp" "${path_conf_storelog_smp}.${date:-date-failed}"
fi
GRN='\033[0;32m'
YLW='\033[1;33m'
BLU='\033[1;34m'
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() {
if [ -e "$path_conf_storelog_xftp" ]; then
cp "$path_conf_storelog_xftp" "${path_conf_storelog_xftp}.${date:-date-failed}"
fi
xftp_variables() {
path_conf_xftp="$path_conf_var/simplex-xftp"
path_conf_xftp_archive="$path_conf_xftp/backups"
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
backup_smp
elif [ "$1" = 'xftp-server' ]; then
backup_xftp
else
backup_smp
backup_xftp
fi
checks() {
result=${SERVICE_RESULT:-exit-code}
status=${EXIT_STATUS:-TERM}
case "$result" in
success)
case "$status" in
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
name: simplexmq
version: 6.0.3.0
version: 6.0.5.0
synopsis: SimpleXMQ message broker
description: This package includes <./docs/Simplex-Messaging-Server.html server>,
<./docs/Simplex-Messaging-Client.html client> and
@@ -610,6 +610,7 @@ test-suite simplexmq-test
CoreTests.CryptoTests
CoreTests.EncodingTests
CoreTests.RetryIntervalTests
CoreTests.SOCKSSettings
CoreTests.TRcvQueuesTests
CoreTests.UtilTests
CoreTests.VersionRangeTests
+3 -2
View File
@@ -38,6 +38,7 @@ import Simplex.Messaging.Client
defaultNetworkConfig,
proxyUsername,
transportClientConfig,
clientSocksCredentials,
unexpectedResponse,
)
import qualified Simplex.Messaging.Crypto as C
@@ -100,7 +101,7 @@ defaultXFTPClientConfig =
getXFTPClient :: TransportSession FileResponse -> XFTPClientConfig -> (XFTPClient -> IO ()) -> IO (Either XFTPClientError XFTPClient)
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
useHost <- liftEither $ chooseTransportHost xftpNetworkConfig host
let tcConfig = (transportClientConfig xftpNetworkConfig useHost) {alpn = clientALPN}
@@ -108,7 +109,7 @@ getXFTPClient transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN,
clientVar <- newTVarIO Nothing
let usePort = if null port then "443" else port
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
v = VersionXFTP 1
thServerVRange = versionToRange v
+6 -3
View File
@@ -33,7 +33,6 @@ import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe, isJust)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, getCurrentTime)
import Data.Time.Clock.System (SystemTime (..), getSystemTime)
import Data.Time.Format.ISO8601 (iso8601Show)
import Data.Word (Word32)
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.Server (dummyVerifyCmd, verifyCmdAuthorization)
import Simplex.Messaging.Server.Expiration
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime, getRoundedSystemTime)
import Simplex.Messaging.Server.Stats
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
@@ -399,7 +399,7 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
r <- runExceptT $ do
sizes <- asks $ allowedChunkSizes . config
unless (size file `elem` sizes) $ throwE SIZE
ts <- liftIO getSystemTime
ts <- liftIO getFileTime
-- TODO validate body empty
sId <- ExceptT $ addFileRetry st file 3 ts
rcps <- mapM (ExceptT . addRecipientRetry st 3 sId) rks
@@ -412,7 +412,7 @@ processXFTPRequest HTTP2Body {bodyPart} = \case
let rIds = L.map (\(FileRecipient rId _) -> rId) rcps
pure $ FRSndIds sId rIds
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 =
retryAdd n $ \sId -> runExceptT $ do
ExceptT $ addFile st sId file ts
@@ -531,6 +531,9 @@ deleteServerFile_ FileRec {senderId, fileInfo, filePath} = do
liftIO $ atomicModifyIORef'_ (filesCount stats) (subtract 1)
liftIO $ atomicModifyIORef'_ (filesSize stats) (subtract $ fromIntegral $ size fileInfo)
getFileTime :: IO RoundedSystemTime
getFileTime = getRoundedSystemTime fileTimePrecision
expireServerFiles :: Maybe Int -> ExpirationConfig -> M ()
expireServerFiles itemDelay expCfg = do
st <- asks store
+10 -6
View File
@@ -17,6 +17,7 @@ module Simplex.FileTransfer.Server.Store
expiredFilePath,
getFile,
ackFile,
fileTimePrecision,
)
where
@@ -25,12 +26,12 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Int (Int64)
import Data.Set (Set)
import qualified Data.Set as S
import Data.Time.Clock.System (SystemTime (..))
import Simplex.FileTransfer.Protocol (FileInfo (..), SFileParty (..), XFTPFileId)
import Simplex.FileTransfer.Transport (XFTPErrorType (..))
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime (..))
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Util (ifM, ($>>=))
@@ -46,9 +47,12 @@ data FileRec = FileRec
fileInfo :: FileInfo,
filePath :: TVar (Maybe FilePath),
recipientIds :: TVar (Set RecipientId),
createdAt :: SystemTime
createdAt :: RoundedSystemTime
}
fileTimePrecision :: Int64
fileTimePrecision = 3600 -- truncate creation time to 1 hour
data FileRecipient = FileRecipient RecipientId RcvPublicAuthKey
instance StrEncoding FileRecipient where
@@ -62,14 +66,14 @@ newFileStore = do
usedStorage <- newTVarIO 0
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 =
ifM (TM.member sId files) (pure $ Left DUPLICATE_) $ do
f <- newFileRec sId fileInfo createdAt
TM.insert sId f files
pure $ Right ()
newFileRec :: SenderId -> FileInfo -> SystemTime -> STM FileRec
newFileRec :: SenderId -> FileInfo -> RoundedSystemTime -> STM FileRec
newFileRec senderId fileInfo createdAt = do
recipientIds <- newTVar S.empty
filePath <- newTVar Nothing
@@ -120,8 +124,8 @@ getFile st party fId = case party of
expiredFilePath :: FileStore -> XFTPFileId -> Int64 -> STM (Maybe (Maybe FilePath))
expiredFilePath FileStore {files} sId old =
TM.lookup sId files
$>>= \FileRec {filePath, createdAt} ->
if systemSeconds createdAt < old
$>>= \FileRec {filePath, createdAt = RoundedSystemTime createdAt} ->
if createdAt + fileTimePrecision < old
then Just <$> readTVar filePath
else pure Nothing
+3 -3
View File
@@ -28,18 +28,18 @@ import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Time.Clock.System (SystemTime)
import Simplex.FileTransfer.Protocol (FileInfo (..))
import Simplex.FileTransfer.Server.Store
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (RcvPublicAuthKey, RecipientId, SenderId)
import Simplex.Messaging.Server.QueueStore (RoundedSystemTime)
import Simplex.Messaging.Server.StoreLog
import Simplex.Messaging.Util (bshow, whenM)
import System.Directory (doesFileExist, renameFile)
import System.IO
data FileStoreLogRecord
= AddFile SenderId FileInfo SystemTime
= AddFile SenderId FileInfo RoundedSystemTime
| PutFile SenderId FilePath
| AddRecipients SenderId (NonEmpty FileRecipient)
| DeleteFile SenderId
@@ -64,7 +64,7 @@ instance StrEncoding FileStoreLogRecord where
logFileStoreRecord :: StoreLog 'WriteMode -> FileStoreLogRecord -> IO ()
logFileStoreRecord = writeStoreLogRecord
logAddFile :: StoreLog 'WriteMode -> SenderId -> FileInfo -> SystemTime -> IO ()
logAddFile :: StoreLog 'WriteMode -> SenderId -> FileInfo -> RoundedSystemTime -> IO ()
logAddFile s = logFileStoreRecord s .:. AddFile
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 qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
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 Simplex.Messaging.Crypto.File (CryptoFile, CryptoFileArgs)
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 Simplex.Messaging.ServiceScheme (ServiceScheme (..))
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.Version
import Simplex.RemoteControl.Client
@@ -2450,17 +2450,14 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), _v, sessId
handleNotifyAck :: AM ACKd -> AM ACKd
handleNotifyAck m = m `catchAgentError` \e -> notify (ERR e) >> ack
SMP.END ->
atomically (TM.lookup tSess (smpClients c) $>>= (tryReadTMVar . sessionVar) >>= processEND)
atomically (ifM (activeClientSession c tSess sessId) (removeSubscription c connId $> True) (pure False))
>>= notifyEnd
where
processEND = \case
Just (Right clnt)
| sessId == sessionId (thParams $ connectedClient clnt) ->
removeSubscription c connId $> True
_ -> pure False
notifyEnd removed
| removed = notify END >> logServer "<--" c srv rId "END"
| 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
r -> unexpected r
where
+7 -4
View File
@@ -277,14 +277,14 @@ supportedSMPAgentVRange = mkVersionRange minSupportedSMPAgentVersion currentSMPA
e2eEncConnInfoLength :: VersionSMPA -> PQSupport -> Int
e2eEncConnInfoLength v = \case
-- reduced by 3726 (roughly the increase of message ratchet header size + key and ciphertext in reply link)
PQSupportOn | v >= pqdrSMPAgentVersion -> 11122
_ -> 14848
PQSupportOn | v >= pqdrSMPAgentVersion -> 11106
_ -> 14832
e2eEncAgentMsgLength :: VersionSMPA -> PQSupport -> Int
e2eEncAgentMsgLength v = \case
-- reduced by 2222 (the increase of message ratchet header size)
PQSupportOn | v >= pqdrSMPAgentVersion -> 13634
_ -> 15856
PQSupportOn | v >= pqdrSMPAgentVersion -> 13618
_ -> 15840
-- | SMP agent event
type ATransmission = (ACorrId, AEntityId, AEvt)
@@ -344,6 +344,7 @@ data AEvent (e :: AEntity) where
INFO :: PQSupport -> ConnInfo -> AEvent AEConn
CON :: PQEncryption -> AEvent AEConn -- notification that connection is established
END :: AEvent AEConn
DELD :: AEvent AEConn
CONNECT :: AProtocolType -> TransportHost -> AEvent AENone
DISCONNECT :: AProtocolType -> TransportHost -> AEvent AENone
DOWN :: SMPServer -> [ConnId] -> AEvent AENone
@@ -413,6 +414,7 @@ data AEventTag (e :: AEntity) where
INFO_ :: AEventTag AEConn
CON_ :: AEventTag AEConn
END_ :: AEventTag AEConn
DELD_ :: AEventTag AEConn
CONNECT_ :: AEventTag AENone
DISCONNECT_ :: AEventTag AENone
DOWN_ :: AEventTag AENone
@@ -466,6 +468,7 @@ aEventTag = \case
INFO {} -> INFO_
CON _ -> CON_
END -> END_
DELD -> DELD_
CONNECT {} -> CONNECT_
DISCONNECT {} -> DISCONNECT_
DOWN {} -> DOWN_
+27 -6
View File
@@ -80,10 +80,12 @@ module Simplex.Messaging.Client
defaultSMPClientConfig,
defaultNetworkConfig,
transportClientConfig,
clientSocksCredentials,
chooseTransportHost,
proxyUsername,
temporaryClientError,
smpProxyError,
textToHostMode,
ServerTransmissionBatch,
ServerTransmission (..),
ClientCommand,
@@ -122,10 +124,13 @@ import Data.List (find)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L
import Data.Maybe (catMaybes, fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime (..), diffUTCTime, getCurrentTime)
import qualified Data.X509 as X
import qualified Data.X509.Validation as XV
import Network.Socket (ServiceName)
import Network.Socks5 (SocksCredentials (..))
import Numeric.Natural
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding
@@ -136,7 +141,7 @@ import Simplex.Messaging.Server.QueueStore.QueueInfo
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
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.WebSockets (WS)
import Simplex.Messaging.Util (bshow, diffToMicroseconds, ifM, liftEitherWith, raceAny_, threadDelay', tshow, whenM)
@@ -236,6 +241,12 @@ data HostMode
HMPublic
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
= -- | always use SOCKS proxy when enabled
SMAlways
@@ -257,7 +268,7 @@ instance StrEncoding SocksMode where
-- | network configuration for the client
data NetworkConfig = NetworkConfig
{ -- | use SOCKS5 proxy
socksProxy :: Maybe SocksProxy,
socksProxy :: Maybe SocksProxyWithAuth,
-- | when to use SOCKS proxy
socksMode :: SocksMode,
-- | 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 {socksProxy = useSocksProxy socksMode, tcpConnectTimeout, tcpKeepAlive, logTLSErrors, clientCredentials = Nothing, alpn = Nothing}
where
useSocksProxy SMAlways = socksProxy
socksProxy' = (\(SocksProxyWithAuth _ proxy) -> proxy) <$> socksProxy
useSocksProxy SMAlways = socksProxy'
useSocksProxy SMOnion = case host of
THOnionHost _ -> socksProxy
THOnionHost _ -> socksProxy'
_ -> Nothing
{-# 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.
data ProtocolClientConfig v = ProtocolClientConfig
{ -- | 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
cVar <- newEmptyTMVarIO
let tcConfig = (transportClientConfig networkConfig useHost) {alpn = clientALPN}
username = proxyUsername transportSession
socksCreds = clientSocksCredentials networkConfig $ proxyUsername transportSession
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)
c_ <- tcpConnectTimeout `timeout` atomically (takeTMVar cVar)
case c_ of
+2 -2
View File
@@ -4,7 +4,7 @@
module Simplex.Messaging.Crypto.SNTRUP761 where
import Crypto.Hash (Digest, SHA3_256, hash)
import Crypto.Hash (Digest, SHA256, hash)
import Data.ByteArray (ScrubbedBytes)
import qualified Data.ByteArray as BA
import Data.ByteString (ByteString)
@@ -28,4 +28,4 @@ kcbEncrypt (KEMHybridSecret k) = sbEncrypt_ k
kemHybridSecret :: PublicKeyX25519 -> PrivateKeyX25519 -> KEMSharedKey -> KEMHybridSecret
kemHybridSecret k pk (KEMSharedKey kem) =
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
| -- | END received
NSEnd
| -- | DELD received (connection was deleted)
NSDeleted
| -- | SMP AUTH error
NSAuth
| -- | SMP error other than AUTH
@@ -456,6 +458,7 @@ ntfShouldSubscribe = \case
NSActive -> True
NSInactive -> True
NSEnd -> False
NSDeleted -> False
NSAuth -> False
NSErr _ -> False
@@ -466,6 +469,7 @@ instance Encoding NtfSubStatus where
NSActive -> "ACTIVE"
NSInactive -> "INACTIVE"
NSEnd -> "END"
NSDeleted -> "DELETED"
NSAuth -> "AUTH"
NSErr err -> "ERR " <> err
smpP =
@@ -475,6 +479,7 @@ instance Encoding NtfSubStatus where
"ACTIVE" -> pure NSActive
"INACTIVE" -> pure NSInactive
"END" -> pure NSEnd
"DELETED" -> pure NSDeleted
"AUTH" -> pure NSAuth
"ERR" -> NSErr <$> (A.space *> A.takeByteString)
_ -> fail "bad NtfSubStatus"
@@ -226,6 +226,7 @@ ntfSubscriber NtfSubscriber {smpSubscribers, newSubQ, smpAgent = ca@SMPClientAge
Right SMP.END ->
whenM (atomically $ activeClientSession' ca sessionId srv) $
updateSubStatus smpQueue NSEnd
Right SMP.DELD -> updateSubStatus smpQueue NSDeleted
Right (SMP.ERR e) -> logError $ "SMP server error: " <> tshow e
Right _ -> logError "SMP server unexpected response"
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 Network.Socket (HostName)
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 qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Notifications.Server (runNtfServer)
@@ -92,6 +92,10 @@ ntfServerCLI cfgPath logPath =
<> "websockets: off\n\n\
\[SUBSCRIBER]\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\
\# You may need a separate instance of SOCKS proxy for incoming single-hop requests.\n\
\# socks_proxy: localhost:9050\n\n\
@@ -134,6 +138,8 @@ ntfServerCLI cfgPath logPath =
defaultNetworkConfig
{ socksProxy = either error id <$!> strDecodeIni "SUBSCRIBER" "socks_proxy" 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
}
},
+8
View File
@@ -484,6 +484,7 @@ data BrokerMsg where
RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy
PRES :: EncResponse -> BrokerMsg -- proxy to client
END :: BrokerMsg
DELD :: BrokerMsg
INFO :: QueueInfo -> BrokerMsg
OK :: BrokerMsg
ERR :: ErrorType -> BrokerMsg
@@ -705,6 +706,7 @@ data BrokerMsgTag
| RRES_
| PRES_
| END_
| DELD_
| INFO_
| OK_
| ERR_
@@ -778,6 +780,7 @@ instance Encoding BrokerMsgTag where
RRES_ -> "RRES"
PRES_ -> "PRES"
END_ -> "END"
DELD_ -> "DELD"
INFO_ -> "INFO"
OK_ -> "OK"
ERR_ -> "ERR"
@@ -794,6 +797,7 @@ instance ProtocolMsgTag BrokerMsgTag where
"RRES" -> Just RRES_
"PRES" -> Just PRES_
"END" -> Just END_
"DELD" -> Just DELD_
"INFO" -> Just INFO_
"OK" -> Just OK_
"ERR" -> Just ERR_
@@ -1423,6 +1427,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
END -> e END_
DELD
| v >= deletedEventSMPVersion -> e DELD_
| otherwise -> e END_
INFO info -> e (INFO_, ' ', info)
OK -> e OK_
ERR err -> e (ERR_, ' ', err)
@@ -1448,6 +1455,7 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
END_ -> pure END
DELD_ -> pure DELD
INFO_ -> INFO <$> _smpP
OK_ -> pure OK
ERR_ -> ERR <$> _smpP
+47 -41
View File
@@ -136,9 +136,11 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
expired <- restoreServerMessages
restoreServerStats expired
raceAny_
( serverThread s "server subscribedQ" subscribedQ subscribers pendingENDs subscriptions cancelSub
: serverThread s "server ntfSubscribedQ" ntfSubscribedQ Env.notifiers pendingNtfENDs ntfSubscriptions (\_ -> pure ())
: sendPendingENDsThread s
( serverThread s "server subscribedQ" True subscribedQ subscribers pendingENDs subscriptions cancelSub
: serverThread s "server deletedQ" False deletedQ subscribers pendingDELDs subscriptions cancelSub
: 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
: map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg
)
@@ -163,13 +165,14 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
forall s.
Server ->
String ->
(Server -> TQueue (QueueId, ClientId, Subscribed)) ->
Subscribed ->
(Server -> TQueue (QueueId, ClientId)) ->
(Server -> TMap QueueId (TVar Client)) ->
(Server -> TVar (IM.IntMap (NonEmpty RecipientId))) ->
(Client -> TMap QueueId s) ->
(s -> IO ()) ->
M ()
serverThread s label subQ subs ends clientSubs unsub = do
serverThread s label subscribed subQ subs pendingEvts clientSubs unsub = do
labelMyThread label
cls <- asks clients
liftIO . forever $
@@ -177,8 +180,8 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
$>>= endPreviousSubscriptions
>>= mapM_ unsub
where
updateSubscribers :: TVar (IM.IntMap (Maybe Client)) -> (QueueId, ClientId, Bool) -> STM (Maybe (QueueId, Client))
updateSubscribers cls (qId, clntId, subscribed) =
updateSubscribers :: TVar (IM.IntMap (Maybe Client)) -> (QueueId, ClientId) -> STM (Maybe (QueueId, Client))
updateSubscribers cls (qId, clntId) =
-- Client lookup by ID is in the same STM transaction.
-- In case client disconnects during the transaction,
-- 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')
endPreviousSubscriptions :: (QueueId, Client) -> IO (Maybe s)
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)
sendPendingENDsThread :: Server -> M ()
sendPendingENDsThread s = do
sendPendingEvtsThread :: Server -> M ()
sendPendingEvtsThread s = do
endInt <- asks $ pendingENDInterval . config
cls <- asks clients
forever $ do
threadDelay endInt
sendPending cls $ pendingENDs s
sendPending cls $ pendingNtfENDs s
sendPending cls END $ pendingENDs s
sendPending cls DELD $ pendingDELDs s
sendPending cls END $ pendingNtfENDs s
sendPending cls DELD $ pendingNtfDELDs s
where
sendPending cls ref = do
sendPending cls evt ref = do
ends <- atomically $ swapTVar ref IM.empty
unless (null ends) $ forM_ (IM.assocs ends) $ \(cId, qIds) ->
mapM_ (queueENDs qIds) . join . IM.lookup cId =<< readTVarIO cls
queueENDs qIds c@Client {connected, sndQ = q} =
mapM_ (queueEvts qIds evt) . join . IM.lookup cId =<< readTVarIO cls
queueEvts qIds evt c@Client {connected, sndQ = q} =
whenM (readTVarIO connected) $ do
sent <- atomically $ ifM (isFullTBQueue q) (pure False) (writeTBQueue q ts $> True)
if sent
then updateEndStats
else -- if queue is full it can block
forkClient c ("sendPendingENDsThread.queueENDs") $
forkClient c ("sendPendingEvtsThread.queueEvts") $
atomically (writeTBQueue q ts) >> updateEndStats
where
ts = L.map (CorrId "",,END) qIds
updateEndStats = do
stats <- asks serverStats
let len = L.length qIds
liftIO $ atomicModifyIORef'_ (qSubEnd stats) (+ len)
liftIO $ atomicModifyIORef'_ (qSubEndB stats) (+ (len `div` 255 + 1)) -- up to 255 ENDs in the batch
ts = L.map (CorrId "",,evt) qIds
updateEndStats = case evt of
END -> do
stats <- asks serverStats
let len = L.length qIds
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 {smpAgent = SMPClientAgent {agentQ}} =
@@ -892,7 +899,7 @@ forkClient Client {endThreads, endThreadSeq} label action = do
mkWeakThreadId t >>= atomically . modifyTVar' endThreads . IM.insert tId
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"
forever $
atomically (readTBQueue rcvQ)
@@ -985,11 +992,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
processCommand (qr_, (corrId, entId, cmd)) = case cmd of
Cmd SProxiedClient command -> processProxiedCmd (corrId, entId, command)
Cmd SSender command -> Just <$> case command of
SKEY sKey -> (corrId,entId,) <$> case qr_ of
Just qr@QueueRec {sndSecure}
| sndSecure -> secureQueue_ "SKEY" qr sKey
| otherwise -> pure $ ERR AUTH
Nothing -> pure $ ERR INTERNAL
SKEY sKey ->
withQueue $ \QueueRec {sndSecure, recipientId} ->
(corrId,entId,) <$> if sndSecure then secureQueue_ "SKEY" recipientId sKey else pure $ ERR AUTH
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
PING -> pure (corrId, NoEntity, PONG)
RFWD encBlock -> (corrId, NoEntity,) <$> processForwardedCommand encBlock
@@ -1009,9 +1014,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
SUB -> withQueue (`subscribeQueue` entId)
GET -> withQueue getMessage
ACK msgId -> withQueue (`acknowledgeMsg` msgId)
KEY sKey -> (corrId,entId,) <$> case qr_ of
Just qr -> secureQueue_ "KEY" qr sKey
Nothing -> pure $ ERR INTERNAL
KEY sKey ->
withQueue $ \QueueRec {recipientId} ->
(corrId,entId,) <$> secureQueue_ "KEY" recipientId sKey
NKEY nKey dhKey -> addQueueNotifier_ st nKey dhKey
NDEL -> deleteQueueNotifier_ st
OFF -> suspendQueue_ st
@@ -1063,10 +1068,9 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
n <- asks $ queueIdBytes . config
liftM2 (,) (randomId n) (randomId n)
secureQueue_ :: T.Text -> QueueRec -> SndPublicAuthKey -> M BrokerMsg
secureQueue_ name qr@QueueRec {recipientId = rId} sKey = time name $ do
secureQueue_ :: T.Text -> RecipientId -> SndPublicAuthKey -> M BrokerMsg
secureQueue_ name rId sKey = time name $ do
withLog $ \s -> logSecureQueue s rId sKey
updateQueueDate qr
st <- asks queueStore
stats <- asks serverStats
incStat $ qSecured stats
@@ -1086,20 +1090,22 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
liftIO (addQueueNotifier st entId ntfCreds) >>= \case
Left DUPLICATE_ -> addNotifierRetry (n - 1) rcvPublicDhKey rcvNtfDhSecret
Left e -> pure $ ERR e
Right _ -> do
Right nId_ -> do
withLog $ \s -> logAddNotifier s entId ntfCreds
incStat . ntfCreated =<< asks serverStats
forM_ nId_ $ \nId -> atomically $ writeTQueue ntfDeletedQ (nId, clientId)
pure $ NID notifierId rcvPublicDhKey
deleteQueueNotifier_ :: QueueStore -> M (Transmission BrokerMsg)
deleteQueueNotifier_ st = do
withLog (`logDeleteNotifier` entId)
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
atomically $ writeTQueue ntfSubscribedQ (entId, clientId, False)
atomically $ writeTQueue ntfDeletedQ (nId, clientId)
incStat . ntfDeleted =<< asks serverStats
pure ok
Right Nothing -> pure ok
Left e -> pure $ err e
suspendQueue_ :: QueueStore -> M (Transmission BrokerMsg)
@@ -1124,7 +1130,7 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
where
newSub :: M Sub
newSub = time "SUB newSub" . atomically $ do
writeTQueue subscribedQ (rId, clientId, True)
writeTQueue subscribedQ (rId, clientId)
sub <- newSubscription NoSub
TM.insert rId sub subscriptions
pure sub
@@ -1199,7 +1205,7 @@ client thParams' clnt@Client {clientId, subscriptions, ntfSubscriptions, rcvQ, s
pure ok
where
newSub = do
writeTQueue ntfSubscribedQ (entId, clientId, True)
writeTQueue ntfSubscribedQ (entId, clientId)
TM.insert entId () ntfSubscriptions
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
Right q -> do
-- 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 ->
atomically $ writeTQueue ntfSubscribedQ (nId, clientId, False)
atomically $ writeTQueue ntfDeletedQ (nId, clientId)
updateDeletedStats q
pure ok
Left e -> pure $ err e
+11 -3
View File
@@ -136,12 +136,16 @@ data Env = Env
type Subscribed = Bool
data Server = Server
{ subscribedQ :: TQueue (RecipientId, ClientId, Subscribed),
{ subscribedQ :: TQueue (RecipientId, ClientId),
deletedQ :: TQueue (RecipientId, ClientId),
subscribers :: TMap RecipientId (TVar Client),
ntfSubscribedQ :: TQueue (NotifierId, ClientId, Subscribed),
ntfSubscribedQ :: TQueue (NotifierId, ClientId),
ntfDeletedQ :: TQueue (NotifierId, ClientId),
notifiers :: TMap NotifierId (TVar Client),
pendingENDs :: TVar (IntMap (NonEmpty RecipientId)),
pendingDELDs :: TVar (IntMap (NonEmpty RecipientId)),
pendingNtfENDs :: TVar (IntMap (NonEmpty NotifierId)),
pendingNtfDELDs :: TVar (IntMap (NonEmpty NotifierId)),
savingLock :: Lock
}
@@ -181,13 +185,17 @@ data Sub = Sub
newServer :: IO Server
newServer = do
subscribedQ <- newTQueueIO
deletedQ <- newTQueueIO
subscribers <- TM.emptyIO
ntfSubscribedQ <- newTQueueIO
ntfDeletedQ <- newTQueueIO
notifiers <- TM.emptyIO
pendingENDs <- newTVarIO IM.empty
pendingDELDs <- newTVarIO IM.empty
pendingNtfENDs <- newTVarIO IM.empty
pendingNtfDELDs <- newTVarIO IM.empty
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 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 Options.Applicative
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 qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String
@@ -307,7 +307,7 @@ smpServerCLI_ generateSite serveStaticFiles cfgPath logPath =
defaultNetworkConfig
{ socksProxy = either error id <$!> strDecodeIni "PROXY" "socks_proxy" 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
}
},
@@ -318,11 +318,6 @@ smpServerCLI_ generateSite serveStaticFiles cfgPath logPath =
serverClientConcurrency = readIniDefault defaultProxyClientConcurrency "PROXY" "client_concurrency" 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 = 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}
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
ifM (TM.memberIO nId notifiers) (pure $ Left DUPLICATE_) $
withQueue rId queues $ \qVar -> do
TM.lookupIO rId queues >>= \case
Just qVar -> atomically $ ifM (TM.member nId notifiers) (pure $ Left DUPLICATE_) $ do
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}
writeTVar qVar q'
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 =
withQueue rId queues $ \qVar -> do
q <- readTVar qVar
forM_ (notifier q) $ \NtfCreds {notifierId} -> TM.delete notifierId notifiers
writeTVar qVar $! q {notifier = Nothing}
forM (notifier q) $ \NtfCreds {notifierId} -> do
TM.delete notifierId notifiers
writeTVar qVar $! q {notifier = Nothing}
pure notifierId
suspendQueue :: QueueStore -> RecipientId -> IO (Either ErrorType ())
suspendQueue QueueStore {queues} rId =
+9 -2
View File
@@ -47,6 +47,7 @@ module Simplex.Messaging.Transport
authCmdsSMPVersion,
sendingProxySMPVersion,
sndAuthKeySMPVersion,
deletedEventSMPVersion,
simplexMQVersion,
smpBlockSize,
TransportConfig (..),
@@ -130,6 +131,9 @@ smpBlockSize = 16384
-- 5 - basic auth for SMP servers (11/12/2022)
-- 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)
-- 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
@@ -160,14 +164,17 @@ sendingProxySMPVersion = VersionSMP 8
sndAuthKeySMPVersion :: VersionSMP
sndAuthKeySMPVersion = VersionSMP 9
deletedEventSMPVersion :: VersionSMP
deletedEventSMPVersion = VersionSMP 10
currentClientSMPRelayVersion :: VersionSMP
currentClientSMPRelayVersion = VersionSMP 9
currentClientSMPRelayVersion = VersionSMP 10
legacyServerSMPRelayVersion :: VersionSMP
legacyServerSMPRelayVersion = VersionSMP 6
currentServerSMPRelayVersion :: VersionSMP
currentServerSMPRelayVersion = VersionSMP 9
currentServerSMPRelayVersion = VersionSMP 10
-- Max SMP protocol version to be used in e2e encrypted
-- connection between client and server, as defined by SMP proxy.
+58 -21
View File
@@ -13,9 +13,13 @@ module Simplex.Messaging.Transport.Client
defaultSMPPort,
defaultTcpConnectTimeout,
defaultTransportClientConfig,
defaultSocksProxyWithAuth,
defaultSocksProxy,
defaultSocksHost,
TransportClientConfig (..),
SocksProxy,
SocksProxy (..),
SocksProxyWithAuth (..),
SocksAuth (..),
TransportHost (..),
TransportHosts (..),
TransportHosts_ (..),
@@ -23,7 +27,7 @@ module Simplex.Messaging.Transport.Client
)
where
import Control.Applicative (optional)
import Control.Applicative (optional, (<|>))
import Control.Logger.Simple (logError)
import Control.Monad (when)
import Data.Aeson (FromJSON (..), ToJSON (..))
@@ -79,7 +83,7 @@ instance StrEncoding TransportHost where
strP =
A.choice
[ 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"),
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 '.')
validIP :: Int -> Either String Word8
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
instance ToJSON TransportHost where
@@ -134,16 +141,16 @@ clientTransportConfig TransportClientConfig {logTLSErrors} =
TransportConfig {logTLSErrors, transportTimeout = Nothing}
-- | 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
runTLSTransportClient :: Transport c => T.Supported -> Maybe XS.CertificateStore -> TransportClientConfig -> Maybe ByteString -> 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 :: 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} socksCreds host port keyHash client = do
serverCert <- newEmptyTMVarIO
let hostName = B.unpack $ strEncode host
clientParams = mkTLSClientParams tlsParams caStore_ hostName port keyHash clientCredentials alpn serverCert
connectTCP = case socksProxy of
Just proxy -> connectSocksClient proxy proxyUsername (hostAddr host)
Just proxy -> connectSocksClient proxy socksCreds (hostAddr host)
_ -> connectTCPClient hostName
c <- do
sock <- connectTCP port
@@ -191,40 +198,70 @@ connectTCPClient host port = withSocketsDo $ resolve >>= tryOpen err
defaultSMPPort :: PortNumber
defaultSMPPort = 5223
connectSocksClient :: SocksProxy -> Maybe ByteString -> SocksHostAddress -> ServiceName -> IO Socket
connectSocksClient (SocksProxy addr) proxyUsername hostAddr _port = do
connectSocksClient :: SocksProxy -> Maybe SocksCredentials -> SocksHostAddress -> ServiceName -> IO Socket
connectSocksClient (SocksProxy addr) socksCreds hostAddr _port = do
let port = if null _port then defaultSMPPort else fromMaybe defaultSMPPort $ readMaybe _port
fst <$> case proxyUsername of
Just username -> socksConnectAuth (defaultSocksConf addr) (SocksAddress hostAddr port) (SocksCredentials username "")
fst <$> case socksCreds of
Just creds -> socksConnectAuth (defaultSocksConf addr) (SocksAddress hostAddr port) creds
_ -> socksConnect (defaultSocksConf addr) (SocksAddress hostAddr port)
defaultSocksHost :: HostAddress
defaultSocksHost = tupleToHostAddress (127, 0, 0, 1)
defaultSocksHost :: (Word8, Word8, Word8, Word8)
defaultSocksHost = (127, 0, 0, 1)
defaultSocksProxyWithAuth :: SocksProxyWithAuth
defaultSocksProxyWithAuth = SocksProxyWithAuth SocksIsolateByAuth defaultSocksProxy
defaultSocksProxy :: SocksProxy
defaultSocksProxy = SocksProxy $ SockAddrInet 9050 defaultSocksHost
defaultSocksProxy = SocksProxy $ SockAddrInet 9050 $ tupleToHostAddress defaultSocksHost
newtype SocksProxy = SocksProxy SockAddr
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 StrEncoding SocksProxy where
strEncode = B.pack . show
strP = do
host <- maybe defaultSocksHost tupleToHostAddress <$> optional ipv4P
host <- fromMaybe (THIPv4 defaultSocksHost) <$> optional strP
port <- fromMaybe 9050 <$> optional (A.char ':' *> (fromInteger <$> A.decimal))
pure . SocksProxy $ SockAddrInet port host
SocksProxy <$> socksAddr port host
where
ipv4P = (,,,) <$> ipNum <*> ipNum <*> ipNum <*> A.decimal
ipNum = A.decimal <* A.char '.'
socksAddr port = \case
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
toEncoding = strToJEncoding
instance FromJSON SocksProxy where
parseJSON = strParseJSON "SocksProxy"
instance FromJSON SocksProxyWithAuth where
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 supported caStore_ host port cafp_ clientCreds_ alpn_ serverCerts =
@@ -11,7 +11,6 @@ import Control.Concurrent.Async
import Control.Exception (IOException, try)
import qualified Control.Exception as E
import Control.Monad
import Data.ByteString.Char8 (ByteString)
import Data.Functor (($>))
import Data.Time (UTCTime, getCurrentTime)
import qualified Data.X509 as X
@@ -20,6 +19,7 @@ import Network.HPACK (BufferSize)
import Network.HTTP2.Client (ClientConfig (..), Request, Response)
import qualified Network.HTTP2.Client as H
import Network.Socket (HostName, ServiceName)
import Network.Socks5 (SocksCredentials)
import qualified Network.TLS as T
import Numeric.Natural (Natural)
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 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 proxyUsername host port keyHash caStore config disconnected = getVerifiedHTTP2ClientWith config host port disconnected setup
getVerifiedHTTP2Client :: Maybe SocksCredentials -> TransportHost -> ServiceName -> Maybe C.KeyHash -> Maybe XS.CertificateStore -> HTTP2ClientConfig -> IO () -> IO (Either HTTP2ClientError HTTP2Client)
getVerifiedHTTP2Client socksCreds host port keyHash caStore config disconnected = getVerifiedHTTP2ClientWith config host port disconnected setup
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 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 {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 tlsParams caStore tcConfig bufferSize proxyUsername host port keyHash = runHTTP2ClientWith bufferSize host setup
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 socksCreds host port keyHash = runHTTP2ClientWith bufferSize host setup
where
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 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; 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; 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
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
+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)
(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
Nothing -> return ()
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 "" _ (NMSG _ _) <- tGet1 nh1
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 "" _ (Msg mId2 msg2) <- tGet1 rh
Resp "5a" _ OK <- signSendRecv rh rKey ("5a", rId, ACK mId2)
@@ -849,6 +854,8 @@ testMessageNotifications (ATransport t) =
Nothing -> pure ()
Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection"
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 "" _ (Msg mId3 msg3) <- tGet1 rh
(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.EncodingTests
import CoreTests.RetryIntervalTests
import CoreTests.SOCKSSettings
import CoreTests.TRcvQueuesTests
import CoreTests.UtilTests
import CoreTests.VersionRangeTests
@@ -52,6 +53,7 @@ main = do
describe "Encryption tests" cryptoTests
describe "Encrypted files tests" cryptoFileTests
describe "Retry interval tests" retryIntervalTests
describe "SOCKS settings tests" socksSettingsTests
describe "TRcvQueues tests" tRcvQueuesTests
describe "Util tests" utilTests
describe "SMP server via TLS" $ serverTests (transport @TLS)
+2 -2
View File
@@ -429,7 +429,7 @@ testXFTPAgentSendRestore = withGlobalLogging logCfgNoLogs $ do
("", sfId', SFPROG _ _) <- sfGet sndr'
liftIO $ sfId' `shouldBe` sfId
threadDelay 100000
threadDelay 200000
withXFTPServerStoreLogOn $ \_ -> do
-- send file - should continue uploading with server up
@@ -443,7 +443,7 @@ testXFTPAgentSendRestore = withGlobalLogging logCfgNoLogs $ do
pure rfd1
-- prefix path should be removed after sending file
threadDelay 100000
threadDelay 200000
doesDirectoryExist prefixPath `shouldReturn` False
doesFileExist encPath `shouldReturn` False