Merge branch 'master' into older-cmake

This commit is contained in:
Fletcher Dunn
2020-08-31 17:28:26 -07:00
29 changed files with 1240 additions and 659 deletions
+9 -7
View File
@@ -99,21 +99,23 @@ if [[ $BUILD_SANITIZERS -ne 0 ]]; then
fi
# Run basic tests
build-cmake-ref/tests/test_crypto
[[ $BUILD_LIBSODIUM -ne 0 ]] && build-cmake-sodium/tests/test_crypto
build-cmake-sodium25519/tests/test_crypto
build-cmake/tests/test_crypto
build-cmake/tests/test_connection
build-cmake-ref/bin/test_crypto
[[ $BUILD_LIBSODIUM -ne 0 ]] && build-cmake-sodium/bin/test_crypto
build-cmake-sodium25519/bin/test_crypto
build-cmake/bin/test_crypto
build-cmake/bin/test_connection
# Run sanitized builds
if [[ $BUILD_SANITIZERS -ne 0 ]]; then
for SANITIZER in asan ubsan tsan; do
[[ -d build-${SANITIZER} ]] || continue
build-${SANITIZER}/tests/test_crypto
build-${SANITIZER}/tests/test_connection
build-${SANITIZER}/bin/test_crypto
build-${SANITIZER}/bin/test_connection
done
fi
# FIXME Run P2P tests?
set +x
exit 0
-48
View File
@@ -1,48 +0,0 @@
#!/bin/bash
#
# Meson build test
#
set -e
cleanup() {
echo "Cleaning up Meson build directories" >&2
rm -rf build-meson{,-ref,-sodium{,25519}}
}
trap cleanup EXIT
cleanup
MESON_ARGS=(
-Dlight_tests=true
-DWerror=true
)
BUILD_LIBSODIUM=1
# libsodium's AES implementation only works on x86_64
[[ $(uname -m) != x86_64 ]] && BUILD_LIBSODIUM=0
set -x
# Build lightweight test builds
meson . build-meson ${MESON_ARGS[@]} --buildtype debugoptimized
meson . build-meson-ref ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto25519=Reference
[[ $BUILD_LIBSODIUM -ne 0 ]] && meson . build-meson-sodium ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto=libsodium -Duse_crypto25519=libsodium
meson . build-meson-sodium25519 ${MESON_ARGS[@]} --buildtype debugoptimized -Duse_crypto25519=libsodium
# Build all targets
ninja -v -C build-meson
[[ $BUILD_LIBSODIUM -ne 0 ]] && ninja -v -C build-meson-sodium
ninja -v -C build-meson-sodium25519
ninja -v -C build-meson-ref
# Run basic tests
build-meson/tests/test_crypto
[[ $BUILD_LIBSODIUM -ne 0 ]] && build-meson-sodium/tests/test_crypto
build-meson-sodium25519/tests/test_crypto
build-meson-ref/tests/test_crypto
set +x
exit 0
+6 -6
View File
@@ -52,8 +52,8 @@ fi
msg "Image is $IMAGE:$IMAGE_TAG, sanitizers enabled: $BUILD_SANITIZERS"
# We need at least one build system available
has cmake || has meson || die "No build system available"
# Make sure cmake is installed
has cmake || die "cmake required"
# We also need at least one compiler
has_clang || has_gcc || die "No compiler available"
@@ -63,16 +63,16 @@ ccache -M4G
if has_clang; then
msg "Beginning build tests with Clang"
export CC="ccache clang" CXX="ccache clang++"
has meson && bash .travis/build-meson.sh
#export CC="ccache clang" CXX="ccache clang++"
#has meson && bash .travis/build-meson.sh
export CC=clang CXX=clang++
has cmake && bash .travis/build-cmake.sh
fi
if has_gcc; then
msg "Beginning build tests with GCC"
export CC="ccache gcc" CXX="ccache g++"
has meson && bash .travis/build-meson.sh
#export CC="ccache gcc" CXX="ccache g++"
#has meson && bash .travis/build-meson.sh
export CC=gcc CXX=g++
has cmake && bash .travis/build-cmake.sh
fi
+1
View File
@@ -18,6 +18,7 @@ PACKAGES=(
pkgconf
git
linux-headers
go
)
PACKAGES+=(protobuf-dev)
+1
View File
@@ -12,6 +12,7 @@ PACKAGES=(
cmake
meson
git
golang
)
PACKAGES+=(protobuf)
+1
View File
@@ -16,6 +16,7 @@ PACKAGES=(
pkgconf-pkg-config
git
make
golang
)
PACKAGES+=(protobuf-compiler protobuf-devel)
+1 -1
View File
@@ -10,7 +10,7 @@ apt-get update
apt-get install -y locales
locale-gen en_US.UTF-8
PACKAGES=(build-essential pkg-config ccache cmake meson clang git)
PACKAGES=(build-essential pkg-config ccache cmake meson clang git golang)
PACKAGES+=(libprotobuf-dev protobuf-compiler)
PACKAGES+=(libssl-dev)
+15 -21
View File
@@ -3,7 +3,8 @@ Building
## Dependencies
* CMake or Meson, and build tool like Ninja, GNU Make or Visual Studio
* CMake 3.10 or later
* A build tool like Ninja, GNU Make or Visual Studio
* A C++11-compliant compiler, such as:
* GCC 7.3 or later
* Clang 3.3 or later
@@ -13,8 +14,12 @@ Building
* OpenSSL 1.1.x, plus ed25519-donna and curve25519-donna. (We've made some
minor changes, so the source is included in this project.)
* libsodium
* [bcrypt](https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/) (windows only)
* [bcrypt](https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/)
(windows only)
* Google protobuf 2.6.1+
* Google [webrtc](https://opensource.google/projects/webrtc) is used for
NAT piercing (ICE) for P2P connections. The relevant code is linked in as a
git submodule. You'll need to initialize that submodule to compile.
## Linux
@@ -38,14 +43,7 @@ Arch Linux:
### Building
Using Meson:
```
$ meson . build
$ ninja -C build
```
Or CMake:
Using CMake (preferred):
```
$ mkdir build
@@ -56,8 +54,7 @@ $ ninja
## Windows / Visual Studio
On Windows, you can use the [vcpkg](https://github.com/microsoft/vcpkg/) package manager. Or setup the
dependencies by hand, which is a bit of an arduous gauntlet.
On Windows, you can use the [vcpkg](https://github.com/microsoft/vcpkg/) package manager.
### vcpkg
@@ -77,6 +74,8 @@ To use libsodium as the crypto backend rather than OpenSSL, install `gamenetwork
### Manual setup
Setting up the dependencies by hand is a bit of an arduous gauntlet.
#### OpenSSL
You can install the [OpenSSL binaries](https://slproweb.com/products/Win32OpenSSL.html)
@@ -224,7 +223,6 @@ a 32-bit build, install the i686 versions of these packages):
$ pacman -S \
git \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-meson \
mingw-w64-x86_64-openssl \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-protobuf
@@ -235,8 +233,10 @@ And finally, clone the repository and build it:
```
$ git clone https://github.com/ValveSoftware/GameNetworkingSockets.git
$ cd GameNetworkingSockets
$ meson . build
$ ninja -C build
$ mkdir build
$ cd build
$ cmake -G Ninja ..
$ ninja
```
**NOTE:** When building with MSYS2, be sure you launch the correct version of
@@ -262,9 +262,3 @@ This extension allows for configuring the CMake project and building it from
within the Visual Studio Code IDE.
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=vector-of-bool.cmake-tools
### Meson by Ali Sabil
This extension comes in handy if you're editing the Meson build files.
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=asabil.meson
+7 -4
View File
@@ -4,6 +4,11 @@ include(CMakeDependentOption)
include(CMakePushCheckState)
include(CheckSymbolExists)
# Put all the output from all projects into the same folder
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
project(GameNetworkingSockets C CXX)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
@@ -140,9 +145,7 @@ endif()
add_subdirectory(src)
message(STATUS "---------------------------------------------------------")
#message(STATUS "---------------------------------------------------------")
message(STATUS "Crypto library for AES/SHA256: ${USE_CRYPTO}")
message(STATUS "Crypto library for ed25519/curve25519: ${USE_CRYPTO25519}")
message(STATUS "---------------------------------------------------------")
# vim: set ts=4 sts=4 sw=4 noet:
#message(STATUS "---------------------------------------------------------")
+5 -2
View File
@@ -69,8 +69,11 @@ to facilitate binding to other languages.
Third party language bindings:
* C#: <https://github.com/nxrighthere/ValveSockets-CSharp>
* Go: <https://github.com/nielsAD/gns/>
* C#:
* <https://github.com/nxrighthere/ValveSockets-CSharp>
* <https://github.com/Facepunch/Facepunch.Steamworks>
* Go:
* <https://github.com/nielsAD/gns/>
## Roadmap
+23 -2
View File
@@ -1,6 +1,29 @@
cmake_minimum_required(VERSION 3.5)
project(gns_example C CXX)
find_library(golang REQUIRED)
#
# Trivial signaling server, written in go
#
set(SIGNAL_SERVER_TARGET trivial_signaling_server)
set(SIGNAL_SERVER_OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/trivial_signaling_server)
set(SIGNAL_SERVER_SRCS
trivial_signaling_server.go
)
add_custom_command(
OUTPUT ${SIGNAL_SERVER_OUTPUT}
DEPENDS ${SIGNAL_SERVER_SRCS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Building GO Trivial signaling server"
COMMAND env go build -o "${SIGNAL_SERVER_OUTPUT}" ${CMAKE_GO_FLAGS} ${SIGNAL_SERVER_SRCS}
)
add_custom_target(${SIGNAL_SERVER_TARGET} ALL DEPENDS ${SIGNAL_SERVER_OUTPUT})
#
# Really simple client/server chat
#
add_executable(
example_chat
example_chat.cpp)
@@ -15,5 +38,3 @@ target_link_libraries(example_chat GameNetworkingSockets::GameNetworkingSockets)
if(COMMAND add_sanitizers)
add_sanitizers(example_chat)
endif()
# vim: set ts=4 sts=4 sw=4 noet:
-12
View File
@@ -1,12 +0,0 @@
cxx_compiler = meson.get_compiler('cpp')
dependencies = [
dep_threads,
dep_GameNetworkingSockets_so # Declared in the other project
]
executable('example_chat',
'example_chat.cpp',
dependencies: dependencies,
)
+363
View File
@@ -0,0 +1,363 @@
// Client of our dummy trivial signaling server service.
// Serves as an example of you how to hook up signaling server
// to SteamNetworkingSockets P2P connections
#include <string>
#include <mutex>
#include <assert.h>
#include "trivial_signaling_client.h"
#include <steam/isteamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#include "../tests/test_common.h"
#ifdef POSIX
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
typedef int SOCKET;
constexpr SOCKET INVALID_SOCKET = -1;
inline void closesocket( SOCKET s ) { close(s); }
inline int GetSocketError() { return errno; }
#endif
#ifdef _WIN32
#endif
inline int HexDigitVal( char c )
{
if ( '0' <= c && c <= '9' )
return c - '0';
if ( 'a' <= c && c <= 'f' )
return c - 'a' + 0xa;
if ( 'A' <= c && c <= 'F' )
return c - 'A' + 0xa;
return -1;
}
/// Implementation of ITrivialSignalingClient
class CTrivialSignalingClient : public ITrivialSignalingClient
{
// This is the thing we'll actually create to send signals for a particular
// connection.
struct ConnectionSignaling : ISteamNetworkingConnectionCustomSignaling
{
CTrivialSignalingClient *const m_pOwner;
std::string const m_sPeerIdentity; // Save off the string encoding of the identity we're talking to
ConnectionSignaling( CTrivialSignalingClient *owner, const char *pszPeerIdentity )
: m_pOwner( owner )
, m_sPeerIdentity( pszPeerIdentity )
{
}
//
// Implements ISteamNetworkingConnectionCustomSignaling
//
// This is called from SteamNetworkingSockets to send a signal. This could be called from any thread,
// so we need to be threadsafe, and avoid duoing slow stuff or calling back into SteamNetworkingSockets
virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) override
{
// We'll use a dumb hex encoding.
std::string signal;
signal.reserve( m_sPeerIdentity.length() + cbMsg*2 + 4 );
signal.append( m_sPeerIdentity );
signal.push_back( ' ' );
for ( const uint8_t *p = (const uint8_t *)pMsg ; cbMsg > 0 ; --cbMsg, ++p )
{
static const char hexdigit[] = "0123456789abcdef";
signal.push_back( hexdigit[ *p >> 4U ] );
signal.push_back( hexdigit[ *p & 0xf ] );
}
signal.push_back('\n');
m_pOwner->Send( signal );
}
// Self destruct. This will be called by SteamNetworkingSockets when it's done with us.
virtual void Release() override
{
delete this;
}
};
sockaddr_storage m_adrServer;
size_t const m_adrServerSize;
ISteamNetworkingSockets *const m_pSteamNetworkingSockets;
std::string m_sGreeting;
std::mutex sockMutex;
SOCKET m_sock;
std::string m_sBufferedData;
void CloseSocket()
{
if ( m_sock != INVALID_SOCKET )
{
closesocket( m_sock );
m_sock = INVALID_SOCKET;
}
m_sBufferedData.clear();
}
void Connect()
{
CloseSocket();
int sockType = SOCK_STREAM;
#ifdef LINUX
sockType |= SOCK_CLOEXEC;
#endif
#if !defined( _WIN32 )
sockType |= SOCK_NONBLOCK;
#endif
m_sock = socket( m_adrServer.ss_family, sockType, IPPROTO_TCP );
if ( m_sock == INVALID_SOCKET )
{
TEST_Printf( "socket() failed, error=%d\n", GetSocketError() );
return;
}
#ifdef _WIN32
opt = 1;
if ( ioctlsocket( sock, FIONBIO, (unsigned long*)&opt ) == -1 )
{
CloseSocket();
TEST_Printf( "ioctlsocket() failed, error=%d\n", GetSocketError() );
return;
}
#endif
connect( m_sock, (const sockaddr *)&m_adrServer, (socklen_t )m_adrServerSize );
}
public:
CTrivialSignalingClient( const sockaddr *adrServer, size_t adrServerSize, ISteamNetworkingSockets *pSteamNetworkingSockets )
: m_adrServerSize( adrServerSize ), m_pSteamNetworkingSockets( pSteamNetworkingSockets )
{
memcpy( &m_adrServer, adrServer, adrServerSize );
m_sock = INVALID_SOCKET;
// Save off our identity
SteamNetworkingIdentity identitySelf; identitySelf.Clear();
pSteamNetworkingSockets->GetIdentity( &identitySelf );
assert( !identitySelf.IsInvalid() );
assert( !identitySelf.IsLocalHost() ); // We need something more specific than that
m_sGreeting = SteamNetworkingIdentityRender( identitySelf ).c_str();
assert( strchr( m_sGreeting.c_str(), ' ' ) == nullptr ); // Our protocol is dumb and doesn't support this
m_sGreeting.push_back( '\n' );
// Begin connecting immediately
Connect();
}
// Send the signal.
void Send( const std::string &s )
{
assert( s.length() > 0 && s[ s.length()-1 ] == '\n' ); // All of our signals are '\n'-terminated
sockMutex.lock();
if ( m_sock != INVALID_SOCKET )
{
int l = s.length();
int r = ::send( m_sock, s.c_str(), s.length(), 0 );
if ( r != l && r != 0 )
{
// Socket hosed, or we sent a partial signal.
// We need to restart connection
TEST_Printf( "Failed to send %d bytes to trivial signaling server. send() returned %d, errno=%d. Closing and restarting connection.\n",
l, r, GetSocketError() );
CloseSocket();
}
}
sockMutex.unlock();
}
//
// Implements ISteamNetworkingCustomSignalingService
//
virtual ISteamNetworkingConnectionCustomSignaling *CreateSignalingForConnection(
const SteamNetworkingIdentity &identityPeer,
const char *pszRoutingInfo,
SteamNetworkingErrMsg &errMsg
) override {
SteamNetworkingIdentityRender sIdentityPeer( identityPeer );
// FIXME - here we really ouight to confirm that the string version of the
// identity does not have spaces, since our protocol doesn't permit it.
return new ConnectionSignaling( this, sIdentityPeer.c_str() );
}
virtual void Poll() override
{
// Drain the socket into the buffer, and check for reconnecting
sockMutex.lock();
if ( m_sock == INVALID_SOCKET )
{
Connect();
}
else
{
for (;;)
{
char buf[256];
int r = recv( m_sock, buf, sizeof(buf), 0 );
if ( r == 0 )
break;
if ( r < 0 )
{
int e = GetSocketError();
if ( e != EAGAIN && e != EWOULDBLOCK )
{
TEST_Printf( "Failed to recv from trivial signaling server. recv() returned %d, errno=%d. Closing and restarting connection\n", r, e );
CloseSocket();
}
break;
}
m_sBufferedData.append( buf, r );
}
}
// Release the lock now. See the notes below about why it's very important
// to release the lock early and not hold it while we try to dispatch the
// received callbacks.
sockMutex.unlock();
// Now dispatch any buffered signals
for (;;)
{
// Find end of line. Do we have a complete signal?
size_t l = m_sBufferedData.find( '\n' );
if ( l == std::string::npos )
break;
// Locate the space that seperates [from] [payload]
size_t spc = m_sBufferedData.find( ' ' );
if ( spc != std::string::npos && spc < l )
{
// Hex decode the payload. As it turns out, we actually don't
// need the sender's identity. The payload has everything needed
// to process the message. Maybe we should remove it from our
// dummy signaling protocol? It might be useful for debugging, tho.
std::string data; data.reserve( ( l - spc ) / 2 );
for ( size_t i = spc+1 ; i+2 <= l ; i += 2 )
{
int h = HexDigitVal( m_sBufferedData[i] );
int l = HexDigitVal( m_sBufferedData[i+1] );
if ( ( h | l ) & ~0xf )
{
// Failed hex decode. Not a bug in our code here, but this is just example code, so we'll handle it this way
assert( !"Failed hex decode from signaling server?!" );
goto next_message;
}
data.push_back( (char)(h<<4 | l ) );
}
// Setup a context object that can respond if this signal is a connection request.
struct Context : ISteamNetworkingCustomSignalingRecvContext
{
CTrivialSignalingClient *m_pOwner;
virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest(
HSteamNetConnection hConn,
const SteamNetworkingIdentity &identityPeer,
int nLocalVirtualPort
) override {
// We will just always handle requests thorugh the usual listen socket state
// machine. See the docuemntation for this function for other behaviour we
// might take.
// Also, note that if there was routing/session info, it should have been in
// our envelope that we know how to parse, and we should save it off in this
// context object.
SteamNetworkingErrMsg ignoreErrMsg;
return m_pOwner->CreateSignalingForConnection( identityPeer, nullptr, ignoreErrMsg );
}
virtual void SendRejectionSignal(
const SteamNetworkingIdentity &identityPeer,
const void *pMsg, int cbMsg
) override {
// We'll just silently ignore all failures. This is actually the more secure
// Way to handle it in many cases. Actively returning failure might allow
// an attacker to just scrape random peers to see who is online. If you know
// the peer has a good reason for trying to connect, sending an active failure
// can improve error handling and the UX, instead of relying on timeout. But
// just consider the security implications.
}
};
Context context;
context.m_pOwner = this;
// Dispatch.
// Remember: From inside this function, our context object might get callbacks.
// And we might get asked to send signals, either now, or really at any time
// from any thread! If possible, avoid calling this function while holding locks.
// To process this call, SteamnetworkingSockets will need take its own internal lock.
// That lock may be held by another thread that is asking you to send a signal! So
// be warned that deadlocks are a possibility here.
m_pSteamNetworkingSockets->ReceivedP2PCustomSignal( data.c_str(), data.length(), &context );
}
next_message:
m_sBufferedData.erase( 0, l );
}
}
virtual void Release()
{
// NOTE: Here we are assuming that the calling code has already cleaned
// up all the connections, to keep the example simple.
CloseSocket();
}
};
// Start connecting to the signaling server.
ITrivialSignalingClient *CreateTrivialSignalingClient(
const char *pszServerAddress, // Address of the server.
ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them?
SteamNetworkingErrMsg &errMsg // Error message is retjrned here if we fail
) {
std::string sAddress( pszServerAddress );
std::string sService;
size_t colon = sAddress.find( ':' );
if ( colon == std::string::npos )
{
sService = "10000"; // Default port
}
else
{
sService = sAddress.substr( colon+1 );
sAddress.erase( colon );
}
// Resolve name synchronously
addrinfo *pAddrInfo = nullptr;
int r = getaddrinfo( sAddress.c_str(), sService.c_str(), nullptr, &pAddrInfo );
if ( r != 0 || pAddrInfo == nullptr )
{
sprintf( errMsg, "Invalid/unknown server address. getaddrinfo returned %d", r );
return nullptr;
}
auto *pClient = new CTrivialSignalingClient( pAddrInfo->ai_addr, pAddrInfo->ai_addrlen, pSteamNetworkingSockets );
freeaddrinfo( pAddrInfo );
return pClient;
}
+57
View File
@@ -0,0 +1,57 @@
// Client of our dummy trivial signaling server service.
// Serves as an example of you how to hook up signaling server
// to SteamNetworkingSockets P2P connections
#pragma once
#include <steam/steamnetworkingcustomsignaling.h>
class ISteamNetworkingSockets;
// FIXME - Eventually I intend to add a mechanism to set the default
// signaling service, so that SteamnetworkingSockets can
// initiate creation of signaling sessions. This will be the
// interface used for that.
/// Interface used to create signaling sessions to particular peers.
/// Typically this represents a connection to some service.
class ISteamNetworkingCustomSignalingService
{
public:
/// Setup a session for sending signals for a particular connection.
/// The signals will always be sent to the same peer.
///
/// pszSessionRoutingInfo is reserved for future use, it will always
/// be NULL right now.
///
/// On failure, return NULL
virtual ISteamNetworkingConnectionCustomSignaling *CreateSignalingForConnection(
const SteamNetworkingIdentity &identityPeer,
const char *pszRoutingInfo,
SteamNetworkingErrMsg &errMsg
) = 0;
};
/// Interface to our client.
class ITrivialSignalingClient : public ISteamNetworkingCustomSignalingService
{
public:
/// Poll the server for incoming signals and dispatch them.
/// We use polling in this example just to keep it simple.
/// You could use a service thread.
virtual void Poll() = 0;
/// Disconnect from the server and close down our polling thread.
virtual void Release() = 0;
};
// Start connecting to the signaling server.
ITrivialSignalingClient *CreateTrivialSignalingClient(
const char *address, // Address:port
ISteamNetworkingSockets *pSteamNetworkingSockets, // Where should we send signals when we get them?
SteamNetworkingErrMsg &errMsg // Error message is retjrned here if we fail
);
+164
View File
@@ -0,0 +1,164 @@
// Really simple P2P signaling server.
//
// When establishing peer-to-peer connections, the peers
// need some sort of pre-arranged side channels that they
// can use to exchange messages. This channel is assumed
// to be relatively low bandwidth and high latency. This
// service is often called "signaling".
//
// This server has the following really simple protocol:
// It listens on a particular TCP port. Clients connect
// raw TCP. The protocol is text-based and line oriented,
// so it is easy to test using telnet. When a client
// connects, it should send its identity on the first line.
// Afterwards, clients can send a message to a peer by
// sending a line formatted as follows:
//
// DESTINATION_IDENTITY PAYLOAD
//
// Identites may not contain spaces, and the payload
// should be plain ASCII text. (Hex or base64 encode it).
//
// If there is a client with that destination identity,
// then the server will forward the message on. Otherwise
// it is discarded.
//
// Forwarded messages have basically the same format and
// are the only type of message the server ever sends to the
// client. The only difference is that the identity is the
// identity of the sender.
//
// This is just an example code to illustrate what a
// signaling service is. A real production server would
// probably need to be able to scale across multiple
// processes, and provide authentication and rate
// limiting.
//
// Note that SteamNetworkingSockets use of signaling
// service does NOT assume guaranteed delivery.
package main
import (
"strings"
"fmt"
"net"
"flag"
"bufio"
"log"
)
const DEFAULT_LISTEN_PORT = 10000
// Current list of client connections
var g_mapClientConnections = make(map[string]net.Conn)
// Goroutine to service a client connection
func ServiceConnection(conn net.Conn) {
// Save off address
addr := conn.RemoteAddr().String()
// Attach a Reader object to the connection, so we can read from it easily
in := bufio.NewReader(conn)
// In our trivial protocol, the first line contains the client identity
// on a line by itself
intro,err := in.ReadString('\n')
if err != nil {
log.Printf( "[%s] Aborting connection before we ever received client identity", addr )
conn.Close()
}
identity := strings.TrimSpace(intro)
// Amnnnnnd that's it. No authentication.
// Locate existing connection, if any.
existingConn := g_mapClientConnections[identity]
// Add us to map or replace existing entry
g_mapClientConnections[identity] = conn
// Now handle existing entry
if existingConn != nil {
log.Printf( "[%s@%s] Closing connection to make room for new connection from '%s'", identity, existingConn.RemoteAddr().String(), addr )
existingConn.Close()
}
log.Printf( "[%s@%s] Added connection", identity, addr )
// Keep reading until connection is closed
for {
line,err := in.ReadString('\n')
if err != nil {
conn.Close()
// Are we stil in the map?
if g_mapClientConnections[identity] == conn {
log.Printf( "[%s@%s] Connecton closed. %s", identity, addr, err )
delete(g_mapClientConnections, identity)
} else {
// Assume it's because we got replaced by another connection.
// The other connection already logged, so don't do anything here
}
break;
}
// Our protocol is just [destination peer identity] [payload]
// And everything is in text.
dest_and_msg := strings.SplitN(line, " ", 2)
if len(dest_and_msg) != 2 {
log.Printf("[%s@%s] Ignoring weird input '%s' (maybe truncated?)", identity, addr, line)
continue
}
dest_identity := strings.TrimSpace(dest_and_msg[0]);
payload := dest_and_msg[1]
// Locate the destination peer's connection.
dest_conn := g_mapClientConnections[dest_identity]
if dest_conn == nil {
log.Printf("[%s@%s] Ignoring, destination peer '%s' not found", identity, addr, dest_identity)
continue
}
// Format new message, putting the sender's identity in front.
msg := identity + " " + payload
// Send to the peer
dest_conn.Write([]byte(msg))
// Lawg
log.Printf("[%s@%s] -> %s (%d chars)", identity, addr, dest_identity, len(payload))
}
}
// Main entry point
func main() {
// Parse command line flags
port := flag.Int("port", DEFAULT_LISTEN_PORT, "Port to listen on")
flag.Parse()
listen_addr := fmt.Sprintf("0.0.0.0:%d", *port)
// Start listening
listener, err := net.Listen("tcp", listen_addr)
if err != nil {
log.Panic(err)
}
log.Printf("Listening at %s", listen_addr)
// Main loop
for {
// Wait for the next incoming connection
conn,err := listener.Accept()
if err != nil {
log.Panic(err)
}
// Start goroutine to service it
go ServiceConnection(conn)
}
}
+7 -79
View File
@@ -557,9 +557,9 @@ public:
/// NOTE: The routing blob returned here is not encrypted. Send it to your backend
/// and don't share it directly with clients.
virtual EResult GetGameCoordinatorServerLogin( SteamDatagramGameCoordinatorServerLogin *pLoginInfo, int *pcbSignedBlob, void *pBlob ) = 0;
#endif // #ifndef STEAMNETWORKINGSOCKETS_ENABLE_SDR
//
// Relayed connections using custom signaling protocol
//
@@ -661,87 +661,15 @@ protected:
};
#define STEAMNETWORKINGSOCKETS_INTERFACE_VERSION "SteamNetworkingSockets009"
/// Interface used to send signaling messages for a particular connection.
/// You will need to construct one of these per connection.
///
/// - For connections initiated locally, you will construct it and pass
/// it to ISteamNetworkingSockets::ConnectP2PCustomSignaling.
/// - For connections initiated remotely and "accepted" locally, you
/// will return it from ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest
class ISteamNetworkingConnectionCustomSignaling
{
public:
/// Called to send a rendezvous message to the remote peer. This may be called
/// from any thread, at any time, so you need to be thread-safe! Don't take
/// any locks that might hold while calling into SteamNetworkingSockets functions,
/// because this could lead to deadlocks.
///
/// Note that when initiating a connection, we may not know the identity
/// of the peer, if you did not specify it in ConnectP2PCustomSignaling.
///
/// Return true if a best-effort attempt was made to deliver the message.
/// If you return false, it is assumed that the situation is fatal;
/// the connection will be closed, and Release() will be called
/// eventually.
///
/// Signaling objects will not be shared between connections.
/// You can assume that the same value of hConn will be used
/// every time.
virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) = 0;
/// Called when the connection no longer needs to send signals.
/// Note that this happens eventually (but not immediately) after
/// the connection is closed. Signals may need to be sent for a brief
/// time after the connection is closed, to clean up the connection.
virtual void Release() = 0;
};
/// Interface used when a custom signal is received.
/// See ISteamNetworkingSockets::ReceivedP2PCustomSignal
class ISteamNetworkingCustomSignalingRecvContext
{
public:
/// Called when the signal represents a request for a new connection.
///
/// If you want to ignore the request, just return NULL. In this case,
/// the peer will NOT receive any reply. You should consider ignoring
/// requests rather than actively rejecting them, as a security measure.
/// If you actively reject requests, then this makes it possible to detect
/// if a user is online or not, just by sending them a request.
///
/// If you wish to send back a rejection, then use
/// ISteamNetworkingSockets::CloseConnection() and then return NULL.
/// We will marshal a properly formatted rejection signal and
/// call SendRejectionSignal() so you can send it to them.
///
/// If you return a signaling object, the connection is NOT immediately
/// accepted by default. Instead, it stays in the "connecting" state,
/// and the usual callback is posted, and your app can accept the
/// connection using ISteamNetworkingSockets::AcceptConnection. This
/// may be useful so that these sorts of connections can be more similar
/// to your application code as other types of connections accepted on
/// a listen socket. If this is not useful and you want to skip this
/// callback process and immediately accept the connection, call
/// ISteamNetworkingSockets::AcceptConnection before returning the
/// signaling object.
///
/// After accepting a connection (through either means), the connection
/// will transition into the "finding route" state.
virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest( HSteamNetConnection hConn, const SteamNetworkingIdentity &identityPeer, int nLocalVirtualPort ) = 0;
/// This is called actively communication rejection or failure
/// to the incoming message. If you intend to ignore all incoming requests
/// that you do not wish to accept, then it's not strictly necessary to
/// implement this.
virtual void SendRejectionSignal( const SteamNetworkingIdentity &identityPeer, const void *pMsg, int cbMsg ) = 0;
};
// Global accessor.
#if defined( STEAMNETWORKINGSOCKETS_PARTNER )
// Standalone lib
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets();
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamGameServerNetworkingSockets();
// Standalone lib. Use different symbol name, so that we can dynamically switch between steamclient.dll
// and the standalone lib
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets_Lib();
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamGameServerNetworkingSockets_Lib();
inline ISteamNetworkingSockets *SteamNetworkingSockets() { return SteamNetworkingSockets_Lib(); }
inline ISteamNetworkingSockets *SteamGameServerNetworkingSockets() { return SteamGameServerNetworkingSockets_Lib(); }
#elif defined( STEAMNETWORKINGSOCKETS_OPENSOURCE ) || defined( STEAMNETWORKINGSOCKETS_STREAMINGCLIENT )
@@ -0,0 +1,92 @@
//====== Copyright Valve Corporation, All rights reserved. ====================
//
// Interfaces needed to implement your own P2P signaling service. If you
// aren't using P2P connections, or you can use the default service provided
// by the platform (e.g. a typical Steam game), then you don't need anything
// in this file.
//
//=============================================================================
#ifndef STEAMNETWORKINGCUSTOMSIGNALING
#define STEAMNETWORKINGCUSTOMSIGNALING
#pragma once
#include "steamnetworkingtypes.h"
/// Interface used to send signaling messages for a particular connection.
///
/// - For connections initiated locally, you will construct it and pass
/// it to ISteamNetworkingSockets::ConnectP2PCustomSignaling.
/// - For connections initiated remotely and "accepted" locally, you
/// will return it from ISteamNetworkingCustomSignalingRecvContext::OnConnectRequest
class ISteamNetworkingConnectionCustomSignaling
{
public:
/// Called to send a rendezvous message to the remote peer. This may be called
/// from any thread, at any time, so you need to be thread-safe! Don't take
/// any locks that might hold while calling into SteamNetworkingSockets functions,
/// because this could lead to deadlocks.
///
/// Note that when initiating a connection, we may not know the identity
/// of the peer, if you did not specify it in ConnectP2PCustomSignaling.
///
/// Return true if a best-effort attempt was made to deliver the message.
/// If you return false, it is assumed that the situation is fatal;
/// the connection will be closed, and Release() will be called
/// eventually.
///
/// Signaling objects will not be shared between connections.
/// You can assume that the same value of hConn will be used
/// every time.
virtual bool SendSignal( HSteamNetConnection hConn, const SteamNetConnectionInfo_t &info, const void *pMsg, int cbMsg ) = 0;
/// Called when the connection no longer needs to send signals.
/// Note that this happens eventually (but not immediately) after
/// the connection is closed. Signals may need to be sent for a brief
/// time after the connection is closed, to clean up the connection.
virtual void Release() = 0;
};
/// Interface used when a custom signal is received.
/// See ISteamNetworkingSockets::ReceivedP2PCustomSignal
class ISteamNetworkingCustomSignalingRecvContext
{
public:
/// Called when the signal represents a request for a new connection.
///
/// If you want to ignore the request, just return NULL. In this case,
/// the peer will NOT receive any reply. You should consider ignoring
/// requests rather than actively rejecting them, as a security measure.
/// If you actively reject requests, then this makes it possible to detect
/// if a user is online or not, just by sending them a request.
///
/// If you wish to send back a rejection, then use
/// ISteamNetworkingSockets::CloseConnection() and then return NULL.
/// We will marshal a properly formatted rejection signal and
/// call SendRejectionSignal() so you can send it to them.
///
/// If you return a signaling object, the connection is NOT immediately
/// accepted by default. Instead, it stays in the "connecting" state,
/// and the usual callback is posted, and your app can accept the
/// connection using ISteamNetworkingSockets::AcceptConnection. This
/// may be useful so that these sorts of connections can be more similar
/// to your application code as other types of connections accepted on
/// a listen socket. If this is not useful and you want to skip this
/// callback process and immediately accept the connection, call
/// ISteamNetworkingSockets::AcceptConnection before returning the
/// signaling object.
///
/// After accepting a connection (through either means), the connection
/// will transition into the "finding route" state.
virtual ISteamNetworkingConnectionCustomSignaling *OnConnectRequest( HSteamNetConnection hConn, const SteamNetworkingIdentity &identityPeer, int nLocalVirtualPort ) = 0;
/// This is called to actively communicate rejection or failure
/// to the incoming message. If you intend to ignore all incoming requests
/// that you do not wish to accept, then it's not strictly necessary to
/// implement this.
virtual void SendRejectionSignal( const SteamNetworkingIdentity &identityPeer, const void *pMsg, int cbMsg ) = 0;
};
#endif // STEAMNETWORKINGCUSTOMSIGNALING
-121
View File
@@ -1,121 +0,0 @@
project('GameNetworkingSockets', 'cpp', 'c',
default_options: [
'buildtype=debugoptimized',
],
license: 'BSD'
)
flags_common = [
'-fno-strict-aliasing',
'-fvisibility=hidden',
]
target_os = target_machine.system()
if target_os == 'windows'
flags_common += [
'-fno-stack-protector',
]
else
flags_common += [
'-fstack-protector-strong',
'-fstack-clash-protection',
]
endif
flags_cxx = [
'-fno-rtti',
'-fno-exceptions',
'-fvisibility-inlines-hidden',
]
warn_flags_common = [
'-Wall',
'-Wextra',
# Explicitly disable noisy warnings
'-Wno-documentation',
'-Wno-implicit-fallthrough',
'-Wno-padded',
'-Wno-sign-compare',
'-Wno-sign-conversion',
'-Wno-signed-enum-bitfield',
'-Wno-unknown-pragmas',
'-Wno-unused-const-variable',
'-Wno-unused-local-typedef',
'-Wno-unused-parameter',
'-Wno-nested-anon-types',
'-Wno-format-truncation'
]
werror = get_option('Werror')
if werror
warn_flags_common += [ '-Werror' ]
endif
use_crypto_25519 = get_option('use_crypto25519')
if use_crypto_25519 == 'Reference'
warn_flags_common += [ '-Wno-unused-function' ]
endif
warn_flags_c = [
'-Wimplicit',
'-Wstrict-prototypes',
'-Wno-missing-prototypes',
]
warn_flags_cxx = [
'-Wno-c++98-compat',
'-Wno-c++98-compat-pedantic',
'-Wno-exit-time-destructors',
'-Wno-global-constructors',
'-Wno-non-virtual-dtor',
'-Wno-old-style-cast',
'-Wno-reorder',
# These ones are prolific but don't really matter. Most are in generated
# protobuf code.
'-Wno-zero-as-null-pointer-constant',
'-Wno-missing-variable-declarations',
]
c_compiler = meson.get_compiler('c')
cxx_compiler = meson.get_compiler('cpp')
c_flags = []
c_flags += c_compiler.first_supported_argument(['-std=c11', '-std=gnu99'])
foreach arg : flags_common + warn_flags_common + warn_flags_c
if c_compiler.has_argument(arg)
c_flags += [ arg ]
endif
endforeach
add_project_arguments(c_flags, language: 'c')
cxx_flags = []
cxx_flags += cxx_compiler.first_supported_argument(['-std=c++11', '-std=c++0x'])
foreach arg : flags_common + warn_flags_common + flags_cxx + warn_flags_cxx
if cxx_compiler.has_argument(arg)
cxx_flags += [ arg ]
endif
endforeach
add_project_arguments(cxx_flags, language: 'cpp')
# Preprocessor defines for platform and compiler
cpp_flags = []
if target_os == 'linux'
cpp_flags += ['-DPOSIX', '-DLINUX']
elif target_os == 'darwin'
cpp_flags += ['-DPOSIX', '-DOSX']
elif target_os == 'windows'
cpp_flags += ['-D_WIN32', '-DWIN32', '-D__STDC_FORMAT_MACROS=1', '-D__USE_MINGW_ANSI_STDIO=0']
else
error('Could not identify your target operating system')
endif
add_project_arguments(cpp_flags, language: 'c')
add_project_arguments(cpp_flags, language: 'cpp')
dep_threads = dependency('threads')
subdir('src')
subdir('tests')
subdir('examples')
-21
View File
@@ -1,21 +0,0 @@
option('light_tests',
type: 'boolean',
value: true,
description: 'Use smaller/shorter tests for simple integration testing (e.g. Travis)')
option('Werror',
type: 'boolean',
value: false,
description: 'Enable -Werror flag when compiling')
option('use_crypto',
type: 'combo',
value: 'OpenSSL',
description: 'Crypto library to use for AES/SHA256',
choices: ['OpenSSL', 'libsodium', 'BCrypt'])
option('use_crypto25519',
type: 'combo',
value: 'OpenSSL',
description: 'Crypto library to use for ed25519/curve25519',
choices: ['OpenSSL', 'libsodium', 'Reference'])
-165
View File
@@ -1,165 +0,0 @@
dependencies = []
c_compiler = meson.get_compiler('c')
protoc_bin = find_program('protoc')
protoc = generator(protoc_bin,
output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'],
arguments : ['-I@CURRENT_SOURCE_DIR@/common', '--proto_path=@CURRENT_SOURCE_DIR@', '--cpp_out=@BUILD_DIR@', '@INPUT@'])
use_crypto = get_option('use_crypto')
use_crypto25519 = get_option('use_crypto25519')
if use_crypto == 'OpenSSL' or use_crypto25519 == 'OpenSSL'
min_libcrypto = dependency('libcrypto', version: '>=1.1.0')
good_libcrypto = dependency('libcrypto', version: '>=1.1.1', required: false)
if good_libcrypto.found()
dependencies += [ good_libcrypto ]
else
dependencies += [ min_libcrypto ]
endif
if use_crypto25519 == 'OpenSSL' and not good_libcrypto.found()
error('This version of OpenSSL does not support ed25519/curve25519. Please use -Duse_crypto25519=Reference or upgrade OpenSSL to 1.1.1 or later.')
endif
code = '''#include <openssl/evp.h>
int main(int argc, char **argv) {
EVP_MD_CTX_free(NULL);
return 0;
}
'''
result = c_compiler.links(code, dependencies: dependencies, name: 'EVP API')
if not result
error('Your OpenSSL version appears to be too old. Check that you\'re using OpenSSL 1.1.0 or later.')
endif
endif
if use_crypto == 'libsodium' or use_crypto25519 == 'libsodium'
dependencies += [ dependency('libsodium') ]
endif
dependencies += [
dep_threads,
dependency('protobuf', version: '>=3.0.0'),
]
incdirs = include_directories('.', '../include', 'common', 'public')
cpp_flags = [
'-DSTEAMNETWORKINGSOCKETS_FOREXPORT',
'-DVALVE_CRYPTO_ENABLE_25519',
'-DCRYPTO_DISABLE_ENCRYPT_WITH_PASSWORD',
'-DGOOGLE_PROTOBUF_NO_RTTI',
]
target_os = target_machine.system()
if target_os == 'windows'
dependencies += [
c_compiler.find_library('ws2_32'),
]
endif
protobuf_sources = [
'common/steamnetworkingsockets_messages_certs.proto',
'common/steamnetworkingsockets_messages.proto',
'common/steamnetworkingsockets_messages_udp.proto',
]
sources = [
'common/crypto_textencode.cpp',
'common/keypair.cpp',
'common/steamid.cpp',
'steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp',
'steamnetworkingsockets/clientlib/steamnetworkingsockets_flat.cpp',
'steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp',
'steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp',
'steamnetworkingsockets/clientlib/steamnetworkingsockets_p2p.cpp',
'steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp',
'steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp',
'steamnetworkingsockets/steamnetworkingsockets_certs.cpp',
'steamnetworkingsockets/steamnetworkingsockets_certstore.cpp',
'steamnetworkingsockets/steamnetworkingsockets_shared.cpp',
'steamnetworkingsockets/steamnetworkingsockets_stats.cpp',
'steamnetworkingsockets/steamnetworkingsockets_thinker.cpp',
'tier0/dbg.cpp',
'tier0/platformtime.cpp',
'tier1/bitstring.cpp',
'tier1/ipv6text.c',
'tier1/netadr.cpp',
'tier1/utlbuffer.cpp',
'tier1/utlmemory.cpp',
'vstdlib/strtools.cpp',
]
if use_crypto == 'OpenSSL'
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_VALVEOPENSSL' ]
sources += [
'common/opensslwrapper.cpp',
'common/crypto_openssl.cpp',
]
endif
if use_crypto == 'BCrypt'
dependencies += [ c_compiler.find_library('bcrypt') ]
cpp_flags += [ '-DED25519_HASH_BCRYPT', '-DSTEAMNETWORKINGSOCKETS_CRYPTO_BCRYPT' ]
sources += [ 'common/crypto_bcrypt.cpp' ]
endif
if use_crypto == 'libsodium'
if target_machine.cpu_family() != 'x86' and target_machine.cpu_family() != 'x86_64'
error('-Duse_crypto=libsodium invalid, libsodium AES implementation only works on x86/x86_64 CPUs')
endif
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_LIBSODIUM' ]
sources += [ 'common/crypto_libsodium.cpp' ]
endif
if use_crypto25519 == 'OpenSSL'
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_25519_OPENSSL' ]
sources += [ 'common/crypto_25519_openssl.cpp' ]
endif
if use_crypto25519 == 'libsodium'
cpp_flags += [ '-DSTEAMNETWORKINGSOCKETS_CRYPTO_25519_LIBSODIUM' ]
sources += [ 'common/crypto_25519_libsodium.cpp' ]
endif
if use_crypto25519 == 'Reference'
cpp_flags += [ '-DVALVE_CRYPTO_25519_DONNA' ]
sources += [
'common/crypto_25519_donna.cpp',
'external/curve25519-donna/curve25519.c',
'external/curve25519-donna/curve25519_VALVE_sse2.c',
'external/ed25519-donna/ed25519_VALVE.c',
'external/ed25519-donna/ed25519_VALVE_sse2.c',
]
endif
protobufs = protoc.process(protobuf_sources)
GameNetworkingSockets_static = static_library('GameNetworkingSockets',
sources, protobufs,
c_args: cpp_flags,
cpp_args: cpp_flags,
include_directories: incdirs,
dependencies: dependencies)
dep_GameNetworkingSockets_static = declare_dependency(
include_directories: include_directories( '../include', '.' ),
compile_args: ['-DSTEAMNETWORKINGSOCKETS_STATIC_LINK'],
link_with: GameNetworkingSockets_static )
GameNetworkingSockets_so = library('GameNetworkingSockets',
sources, protobufs,
c_args: cpp_flags,
cpp_args: cpp_flags,
include_directories: incdirs,
dependencies: dependencies)
dep_GameNetworkingSockets_so = declare_dependency(
include_directories: include_directories( '../include', '.' ),
link_with: GameNetworkingSockets_so )
# vim: set ts=2 sts=2 sw=2 et:
@@ -4,6 +4,10 @@
#include "csteamnetworkingsockets.h"
#include "crypto.h"
#ifdef POSIX
#include <dlfcn.h>
#endif
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_SDR
#include "steamnetworkingsockets_sdr_p2p.h"
#endif
@@ -4,6 +4,7 @@
#define STEAMNETWORKINGSOCKETS_P2P_H
#pragma once
#include <steam/steamnetworkingcustomsignaling.h>
#include "steamnetworkingsockets_connections.h"
#include "csteamnetworkingsockets.h"
+12 -2
View File
@@ -7,11 +7,19 @@ endif()
add_executable(
test_connection
test_common.cpp
test_connection.cpp)
add_executable(
test_p2p
test_common.cpp
test_p2p.cpp
../examples/trivial_signaling_client.cpp)
if(SANITIZE)
target_link_libraries(test_connection GameNetworkingSockets_s)
target_link_libraries(test_p2p GameNetworkingSockets_s)
else()
target_link_libraries(test_connection GameNetworkingSockets)
target_link_libraries(test_p2p GameNetworkingSockets)
endif()
add_sanitizers(test_connection)
@@ -23,6 +31,8 @@ target_include_directories(test_crypto PRIVATE ../src ../src/public ../src/commo
target_link_libraries(test_crypto GameNetworkingSockets_s)
add_sanitizers(test_crypto)
file(COPY aesgcmtestvectors DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY aesgcmtestvectors DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# vim: set ts=4 sts=4 sw=4 noet:
# NOTE: This happens when we generate the projects, and really we'd like to actually
# make a build rule to copy it. I don't want to do that work right now.
file(COPY test_p2p.py DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-39
View File
@@ -1,39 +0,0 @@
common_deps = [ dep_threads ]
cppflags = [
'-DVALVE_CRYPTO_ENABLE_25519'
]
light_tests= get_option('light_tests')
if light_tests
cppflags += ['-DLIGHT_TESTS']
endif
executable('test_connection',
'test_connection.cpp',
dependencies: common_deps + [ dep_GameNetworkingSockets_so ],
cpp_args: cppflags,
)
executable('test_crypto',
'test_crypto.cpp',
dependencies: common_deps + [ dep_GameNetworkingSockets_static ],
cpp_args: cppflags + ['-DTEST_VECTOR_DIR="' + join_paths(meson.source_root(), 'tests/aesgcmtestvectors') + '/"'],
include_directories: include_directories('../src', '../src/public', '../src/common')
)
# !FIXME! Ug cannot link with the static lib, because we need to #define the hardcoded key.
# So we'll need the crypto and protobuf dependencies, and those are pretty complicated.
# We need to refactor these files to get that organized.
#executable('test_pki',
# 'test_pki.cpp',
# dependencies: common_deps,
# cpp_args: cppflags + ['-DSTEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMPsJA3FUaQSsHkyYhWvcYt9v3XbwRBcWuVuayWGdK1"'],
# include_directories: include_directories('../src', '../include', '../src/public', '../src/common')
#)
#incdirs = include_directories('../include')
#executable('test_flat',
# 'test_flat.c',
# dependence: dependencies,
# include_directories: incdirs
#)
+120
View File
@@ -0,0 +1,120 @@
// Misc stuff used in the tests
#include "test_common.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <chrono>
#include <thread>
#include <steam/steamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE
#include <steam/steam_api.h>
#endif
static FILE *g_fpLog = nullptr;
static SteamNetworkingMicroseconds g_logTimeZero;
static void DebugOutput( ESteamNetworkingSocketsDebugOutputType eType, const char *pszMsg )
{
SteamNetworkingMicroseconds time = SteamNetworkingUtils()->GetLocalTimestamp() - g_logTimeZero;
if ( g_fpLog )
fprintf( g_fpLog, "%10.6f %s\n", time*1e-6, pszMsg );
if ( eType <= k_ESteamNetworkingSocketsDebugOutputType_Msg )
{
printf( "%10.6f %s\n", time*1e-6, pszMsg );
fflush(stdout);
}
if ( eType == k_ESteamNetworkingSocketsDebugOutputType_Bug )
{
fflush(stdout);
fflush(stderr);
if ( g_fpLog )
fflush( g_fpLog );
// !KLUDGE! Our logging (which is done while we hold the lock)
// is occasionally triggering this assert. Just ignroe that one
// error for now.
// Yes, this is a kludge.
if ( strstr( pszMsg, "SteamDatagramTransportLock held for" ) )
return;
assert( !"TEST FAILED" );
}
}
void TEST_Printf( const char *fmt, ... )
{
char text[ 2048 ];
va_list ap;
va_start( ap, fmt );
vsprintf( text, fmt, ap );
va_end(ap);
char *nl = strchr( text, '\0' ) - 1;
if ( nl >= text && *nl == '\n' )
*nl = '\0';
DebugOutput( k_ESteamNetworkingSocketsDebugOutputType_Msg, text );
}
void TEST_Fatal( const char *fmt, ... )
{
fflush(stdout);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
fflush(stderr);
exit(1);
}
void TEST_Init( const SteamNetworkingIdentity *pIdentity )
{
g_fpLog = fopen( "log.txt", "wt" );
g_logTimeZero = SteamNetworkingUtils()->GetLocalTimestamp();
SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Debug, DebugOutput );
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Verbose, DebugOutput );
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput );
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
SteamDatagramErrMsg errMsg;
if ( !GameNetworkingSockets_Init( pIdentity, errMsg ) )
{
fprintf( stderr, "GameNetworkingSockets_Init failed. %s", errMsg );
exit(1);
}
#else
//SteamAPI_Init();
SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what
//SteamDatagramClient_SetUniverse( k_EUniverseDev );
SteamDatagramErrMsg errMsg;
if ( !SteamDatagramClient_Init( true, errMsg ) )
{
fprintf( stderr, "SteamDatagramClient_Init failed. %s", errMsg );
exit(1);
}
#endif
}
void TEST_Kill()
{
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
GameNetworkingSockets_Kill();
#else
SteamDatagramClient_Kill();
#endif
}
void TEST_PumpCallbacks()
{
SteamNetworkingSockets()->RunCallbacks();
std::this_thread::sleep_for( std::chrono::milliseconds( 2 ) );
}
+11
View File
@@ -0,0 +1,11 @@
// Misc stuff used in the tests
#pragma once
struct SteamNetworkingIdentity;
extern void TEST_Init();
extern void TEST_Printf( const char *fmt, ... );
extern void TEST_Fatal( const char *fmt, ... );
extern void TEST_Init( const SteamNetworkingIdentity *pIdentity );
extern void TEST_Kill();
extern void TEST_PumpCallbacks();
+39 -129
View File
@@ -8,6 +8,8 @@
#include <chrono>
#include <thread>
#include "test_common.h"
#include <steam/steamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#ifndef STEAMNETWORKINGSOCKETS_OPENSOURCE
@@ -19,93 +21,6 @@
static std::default_random_engine g_rand;
static SteamNetworkingMicroseconds g_usecTestElapsed;
FILE *g_fpLog = nullptr;
SteamNetworkingMicroseconds g_logTimeZero;
void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo );
static void DebugOutput( ESteamNetworkingSocketsDebugOutputType eType, const char *pszMsg )
{
SteamNetworkingMicroseconds time = SteamNetworkingUtils()->GetLocalTimestamp() - g_logTimeZero;
if ( g_fpLog )
fprintf( g_fpLog, "%10.6f %s\n", time*1e-6, pszMsg );
if ( eType <= k_ESteamNetworkingSocketsDebugOutputType_Msg )
{
printf( "%10.6f %s\n", time*1e-6, pszMsg );
fflush(stdout);
}
if ( eType == k_ESteamNetworkingSocketsDebugOutputType_Bug )
{
fflush(stdout);
fflush(stderr);
if ( g_fpLog )
fflush( g_fpLog );
// !KLUDGE! Our logging (which is done while we hold the lock)
// is occasionally triggering this assert. Just ignroe that one
// error for now.
// Yes, this is a kludge.
if ( strstr( pszMsg, "SteamDatagramTransportLock held for" ) )
return;
assert( !"TEST FAILED" );
}
}
static void Printf( const char *fmt, ... )
{
char text[ 2048 ];
va_list ap;
va_start( ap, fmt );
vsprintf( text, fmt, ap );
va_end(ap);
char *nl = strchr( text, '\0' ) - 1;
if ( nl >= text && *nl == '\n' )
*nl = '\0';
DebugOutput( k_ESteamNetworkingSocketsDebugOutputType_Msg, text );
}
static void InitSteamDatagramConnectionSockets()
{
g_fpLog = fopen( "log.txt", "wt" );
g_logTimeZero = SteamNetworkingUtils()->GetLocalTimestamp();
SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Debug, DebugOutput );
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Verbose, DebugOutput );
//SteamNetworkingUtils()->SetDebugOutputFunction( k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput );
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged );
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
SteamDatagramErrMsg errMsg;
if ( !GameNetworkingSockets_Init( nullptr, errMsg ) )
{
fprintf( stderr, "GameNetworkingSockets_Init failed. %s", errMsg );
exit(1);
}
#else
//SteamAPI_Init();
SteamDatagramClient_SetAppID( 570 ); // Just set something, doesn't matter what
//SteamDatagramClient_SetUniverse( k_EUniverseDev );
SteamDatagramErrMsg errMsg;
if ( !SteamDatagramClient_Init( true, errMsg ) )
{
fprintf( stderr, "SteamDatagramClient_Init failed. %s", errMsg );
exit(1);
}
#endif
}
static void ShutdownSteamDatagramConnectionSockets()
{
#ifdef STEAMNETWORKINGSOCKETS_OPENSOURCE
GameNetworkingSockets_Kill();
#else
SteamDatagramClient_Kill();
#endif
}
static HSteamListenSocket g_hSteamListenSocket = k_HSteamListenSocket_Invalid;
struct TestMsg
@@ -186,7 +101,7 @@ struct SFakePeer
if ( result != k_EResultOK )
{
Printf( "***ERROR ON Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
TEST_Printf( "***ERROR ON Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
m_sName.c_str(),
g_usecTestElapsed*1e-6,
msg.m_bReliable ? "reliable" : "unreliable",
@@ -197,7 +112,7 @@ struct SFakePeer
}
#if 0
else
Printf( "Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
TEST_Printf( "Send: %s %.3f %s message %lld, %d bytes (pending %d bytes)\n",
connection.m_sName.c_str(),
g_usecTestElapsed*1e-6,
msg.m_bReliable ? "reliable" : "unreliable",
@@ -244,7 +159,7 @@ static void Recv( ISteamNetworkingSockets *pSteamSocketNetworking )
{
// Print that it happened.
Printf(
TEST_Printf(
"Recv: %s, %s MISMATCH NUM wanted %lld got %lld\n",
pConnection->m_sName.c_str(),
pTestMsg->m_bReliable ? "RELIABLE" : "UNRELIABLE",
@@ -278,7 +193,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
{
case k_ESteamNetworkingConnectionState_ClosedByPeer:
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
Printf( "Steam Net connection %x %s, reason %d: %s\n",
TEST_Printf( "Steam Net connection %x %s, reason %d: %s\n",
pInfo->m_hConn,
( pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ? "closed by peer" : "problem detected locally" ),
pInfo->m_info.m_eEndReason,
@@ -301,7 +216,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
/*
case k_ESteamNetworkingConnectionState_None:
Printf( "No steam Net connection %x (%s)\n", pInfo->m_hConn, pInfo->m_info.m_steamIDRemote.Render() );
TEST_Printf( "No steam Net connection %x (%s)\n", pInfo->m_hConn, pInfo->m_info.m_steamIDRemote.Render() );
if ( g_hSteamNetConnection == pInfo->m_hConn )
{
@@ -317,7 +232,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
if ( g_hSteamListenSocket != k_HSteamListenSocket_Invalid && pInfo->m_info.m_hListenSocket == g_hSteamListenSocket )
{
// Somebody's knocking
Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription );
TEST_Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription );
g_peerServer.m_hSteamNetConnection = pInfo->m_hConn;
g_peerServer.m_bIsConnected = true;
SteamNetworkingSockets()->AcceptConnection( pInfo->m_hConn );
@@ -331,7 +246,7 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
{
g_peerClient.m_bIsConnected = true;
}
Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription );
TEST_Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription );
break;
@@ -341,15 +256,9 @@ void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_
}
}
static void PumpCallbacks()
{
SteamNetworkingSockets()->RunCallbacks();
std::this_thread::sleep_for( std::chrono::milliseconds( 2 ) );
}
static void PumpCallbacksAndMakeSureStillConnected()
{
PumpCallbacks();
TEST_PumpCallbacks();
assert( g_peerClient.m_bIsConnected );
assert( g_peerServer.m_bIsConnected );
assert( g_peerServer.m_hSteamNetConnection != k_HSteamNetConnection_Invalid );
@@ -368,35 +277,35 @@ static void PrintStatus( const SFakePeer &p1, const SFakePeer &p2 )
{
const SteamNetworkingQuickConnectionStatus &info1 = p1.m_info;
const SteamNetworkingQuickConnectionStatus &info2 = p2.m_info;
Printf( "\n" );
Printf( "%12s %12s\n", p1.m_sName.c_str(), p2.m_sName.c_str() );
Printf( "%10dms %10dms Ping\n", info1.m_nPing, info2.m_nPing );
Printf( "%12s %12s Quality\n", FormatQuality( info1.m_flConnectionQualityLocal ).c_str(), FormatQuality( info2.m_flConnectionQualityLocal ).c_str() );
Printf( "%11.1fK %11.1fK Send buffer\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )/1024.0f, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )/1024.0f );
Printf( "%11.1fK %11.1fK Send rate (app)\n", p1.m_flSendRate/1024.0f, p2.m_flSendRate/1024.0f );
Printf( "%11.1fK %11.1fK Send rate (wire)\n", info1.m_flOutBytesPerSec/1024.0f, info2.m_flOutBytesPerSec/1024.0f );
Printf( "%12.1f %12.1f Send pkts/sec (wire)\n", info1.m_flOutPacketsPerSec, info2.m_flOutPacketsPerSec );
Printf( "%11.1fK %11.1fK Send bandwidth (estimate)\n", info1.m_nSendRateBytesPerSecond/1024.0f, info2.m_nSendRateBytesPerSecond/1024.0f );
Printf( "%11.1fK %11.1fK Recv rate (app)\n", p1.m_flRecvRate/1024.0f, p2.m_flRecvRate/1024.0f );
Printf( "%11.1fK %11.1fK Recv rate (wire)\n", info1.m_flInBytesPerSec/1024.0f, info2.m_flInBytesPerSec/1024.0f );
Printf( "%12.1f %12.1f Recv pkts/sec (wire)\n", info1.m_flInPacketsPerSec, info2.m_flInPacketsPerSec );
Printf( "%10.1fms %10.1fms Send buffer drain time, based on bandwidth\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )*1000.0f/info1.m_nSendRateBytesPerSecond, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )*1000.0f/info2.m_nSendRateBytesPerSecond );
Printf( "%10.1fms %10.1fms App RTT (reliable)\n", p1.m_flReliableMsgDelay*1e3, p2.m_flReliableMsgDelay*1e3 );
Printf( "%10.1fms %10.1fms App RTT (unreliable)\n", p1.m_flUnreliableMsgDelay*1e3, p2.m_flUnreliableMsgDelay*1e3 );
TEST_Printf( "\n" );
TEST_Printf( "%12s %12s\n", p1.m_sName.c_str(), p2.m_sName.c_str() );
TEST_Printf( "%10dms %10dms Ping\n", info1.m_nPing, info2.m_nPing );
TEST_Printf( "%12s %12s Quality\n", FormatQuality( info1.m_flConnectionQualityLocal ).c_str(), FormatQuality( info2.m_flConnectionQualityLocal ).c_str() );
TEST_Printf( "%11.1fK %11.1fK Send buffer\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )/1024.0f, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )/1024.0f );
TEST_Printf( "%11.1fK %11.1fK Send rate (app)\n", p1.m_flSendRate/1024.0f, p2.m_flSendRate/1024.0f );
TEST_Printf( "%11.1fK %11.1fK Send rate (wire)\n", info1.m_flOutBytesPerSec/1024.0f, info2.m_flOutBytesPerSec/1024.0f );
TEST_Printf( "%12.1f %12.1f Send pkts/sec (wire)\n", info1.m_flOutPacketsPerSec, info2.m_flOutPacketsPerSec );
TEST_Printf( "%11.1fK %11.1fK Send bandwidth (estimate)\n", info1.m_nSendRateBytesPerSecond/1024.0f, info2.m_nSendRateBytesPerSecond/1024.0f );
TEST_Printf( "%11.1fK %11.1fK Recv rate (app)\n", p1.m_flRecvRate/1024.0f, p2.m_flRecvRate/1024.0f );
TEST_Printf( "%11.1fK %11.1fK Recv rate (wire)\n", info1.m_flInBytesPerSec/1024.0f, info2.m_flInBytesPerSec/1024.0f );
TEST_Printf( "%12.1f %12.1f Recv pkts/sec (wire)\n", info1.m_flInPacketsPerSec, info2.m_flInPacketsPerSec );
TEST_Printf( "%10.1fms %10.1fms Send buffer drain time, based on bandwidth\n", ( info1.m_cbPendingReliable+info1.m_cbPendingUnreliable )*1000.0f/info1.m_nSendRateBytesPerSecond, ( info2.m_cbPendingReliable+info2.m_cbPendingUnreliable )*1000.0f/info2.m_nSendRateBytesPerSecond );
TEST_Printf( "%10.1fms %10.1fms App RTT (reliable)\n", p1.m_flReliableMsgDelay*1e3, p2.m_flReliableMsgDelay*1e3 );
TEST_Printf( "%10.1fms %10.1fms App RTT (unreliable)\n", p1.m_flUnreliableMsgDelay*1e3, p2.m_flUnreliableMsgDelay*1e3 );
}
static void TestNetworkConditions( int rate, float loss, int lag, float reorderPct, int reorderLag, bool bActLikeGame )
{
ISteamNetworkingSockets *pSteamSocketNetworking = SteamNetworkingSockets();
Printf( "---------------------------------------------------\n" );
Printf( "NETWORK CONDITIONS\n" );
Printf( "Rate . . . . . . : %d Bps\n", rate );
Printf( "Loss . . . . . . : %g%%\n", loss );
Printf( "Ping . . . . . . : %d\n", lag*2 );
Printf( "Reorder. . . . . : %g%% @ %dms\n", reorderPct, reorderLag );
Printf( "Act like game. . : %d\n", (int)bActLikeGame );
Printf( "---------------------------------------------------\n" );
TEST_Printf( "---------------------------------------------------\n" );
TEST_Printf( "NETWORK CONDITIONS\n" );
TEST_Printf( "Rate . . . . . . : %d Bps\n", rate );
TEST_Printf( "Loss . . . . . . : %g%%\n", loss );
TEST_Printf( "Ping . . . . . . : %d\n", lag*2 );
TEST_Printf( "Reorder. . . . . : %g%% @ %dms\n", reorderPct, reorderLag );
TEST_Printf( "Act like game. . : %d\n", (int)bActLikeGame );
TEST_Printf( "---------------------------------------------------\n" );
SteamNetworkingUtils()->SetGlobalConfigValueInt32( k_ESteamNetworkingConfig_SendRateMin, rate );
SteamNetworkingUtils()->SetGlobalConfigValueInt32( k_ESteamNetworkingConfig_SendRateMax, rate );
@@ -470,14 +379,14 @@ static void TestNetworkConditions( int rate, float loss, int lag, float reorderP
usecWhenStateEnd = g_usecTestElapsed + ( bQuiet ? usecQuietDuration : usecActiveDuration );
if ( nIterations-- <= 0 )
break;
Printf( "Entering active time (sending enabled)\n" );
TEST_Printf( "Entering active time (sending enabled)\n" );
}
}
else
{
bQuiet = true;
usecWhenStateEnd = g_usecTestElapsed + usecQuietDuration;
Printf( "Entering quiet time (no sending) to see how fast queues drain\n" );
TEST_Printf( "Entering quiet time (no sending) to see how fast queues drain\n" );
}
}
@@ -553,7 +462,7 @@ static void RunSteamDatagramConnectionTest()
// Wait for connection to complete
while ( !g_peerClient.m_bIsConnected || !g_peerServer.m_bIsConnected )
PumpCallbacks();
TEST_PumpCallbacks();
auto Test = []( int rate, float loss, int lag, float reorderPct, int reorderLag )
{
@@ -626,12 +535,13 @@ int main( )
TestSteamNetworkingIdentity();
// Create client and server sockets
InitSteamDatagramConnectionSockets();
TEST_Init( nullptr );
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged );
// Run the test
RunSteamDatagramConnectionTest();
ShutdownSteamDatagramConnectionSockets();
TEST_Kill();
return 0;
}
+243
View File
@@ -0,0 +1,243 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <string>
#include <random>
#include <chrono>
#include <thread>
#include "test_common.h"
#include <steam/steamnetworkingsockets.h>
#include <steam/isteamnetworkingutils.h>
#include "../examples/trivial_signaling_client.h"
HSteamListenSocket g_hListenSock;
HSteamNetConnection g_hConnection;
enum ETestRole
{
k_ETestRole_Undefined,
k_ETestRole_Server,
k_ETestRole_Client,
k_ETestRole_Symmetric,
};
ETestRole g_eTestRole = k_ETestRole_Undefined;
int g_nVirtualPortLocal = 0; // Used when listening, and when connecting
int g_nVirtualPortRemote = 0; // Only used when connecting
void OnSteamNetConnectionStatusChanged( SteamNetConnectionStatusChangedCallback_t *pInfo )
{
// What's the state of the connection?
switch ( pInfo->m_info.m_eState )
{
case k_ESteamNetworkingConnectionState_ClosedByPeer:
case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
TEST_Printf( "[%s] %s, reason %d: %s\n",
pInfo->m_info.m_szConnectionDescription,
( pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ? "closed by peer" : "problem detected locally" ),
pInfo->m_info.m_eEndReason,
pInfo->m_info.m_szEndDebug
);
// Close our end
SteamNetworkingSockets()->CloseConnection( pInfo->m_hConn, 0, nullptr, false );
if ( g_hConnection == pInfo->m_hConn )
{
g_hConnection = k_HSteamNetConnection_Invalid;
}
else
{
// Why are we hearing about any another connection?
assert( false );
}
break;
case k_ESteamNetworkingConnectionState_None:
// Notification that a connection was destroyed. (By us, presumably.)
// We don't need this, so ignore it.
break;
case k_ESteamNetworkingConnectionState_Connecting:
// Is this a connection we initiated, or one that we are receiving?
if ( g_hListenSock != k_HSteamListenSocket_Invalid && pInfo->m_info.m_hListenSocket == g_hListenSock )
{
// Somebody's knocking
TEST_Printf( "[%s] Accepting\n", pInfo->m_info.m_szConnectionDescription );
g_hConnection = pInfo->m_hConn;
SteamNetworkingSockets()->AcceptConnection( pInfo->m_hConn );
}
else
{
// Note that we will get notification when our own connection that
// we initiate enters this state.
}
break;
case k_ESteamNetworkingConnectionState_FindingRoute:
// P2P connections will spend a bried time here where they swap addresses
// and try to find a route.
TEST_Printf( "[%s] finding route\n", pInfo->m_info.m_szConnectionDescription );
break;
case k_ESteamNetworkingConnectionState_Connected:
// We got fully connected
assert( pInfo->m_hConn == g_hConnection ); // We don't initiate or accept any other connections, so this should be out own connection
TEST_Printf( "[%s] connected\n", pInfo->m_info.m_szConnectionDescription );
break;
default:
assert( false );
break;
}
}
int main( int argc, const char **argv )
{
SteamNetworkingIdentity identityLocal; identityLocal.Clear();
SteamNetworkingIdentity identityRemote; identityRemote.Clear();
const char *pszTrivialSignalingService = "localhost:10000";
// Parse the command line
for ( int idxArg = 1 ; idxArg < argc ; ++idxArg )
{
const char *pszSwitch = argv[idxArg];
auto GetArg = [&]() -> const char * {
if ( idxArg + 1 >= argc )
TEST_Fatal( "Expected argument after %s", pszSwitch );
return argv[++idxArg];
};
auto ParseIdentity = [&]( SteamNetworkingIdentity &x ) {
const char *pszArg = GetArg();
if ( !x.ParseString( pszArg ) )
TEST_Fatal( "'%s' is not a valid identity string", pszArg );
};
if ( !strcmp( pszSwitch, "--identity-local" ) )
ParseIdentity( identityLocal );
else if ( !strcmp( pszSwitch, "--identity-remote" ) )
ParseIdentity( identityRemote );
else if ( !strcmp( pszSwitch, "--signaling-server" ) )
pszTrivialSignalingService = GetArg();
else if ( !strcmp( pszSwitch, "--client" ) )
g_eTestRole = k_ETestRole_Client;
else if ( !strcmp( pszSwitch, "--server" ) )
g_eTestRole = k_ETestRole_Server;
else if ( !strcmp( pszSwitch, "--symmetric" ) )
g_eTestRole = k_ETestRole_Symmetric;
else
TEST_Fatal( "Unexpected command line argument '%s'", pszSwitch );
}
if ( g_eTestRole == k_ETestRole_Undefined )
TEST_Fatal( "Must specify test role (--server, --client, or --symmetric" );
if ( identityLocal.IsInvalid() )
TEST_Fatal( "Must specify local identity using --identity-local" );
if ( identityRemote.IsInvalid() && g_eTestRole != k_ETestRole_Server )
TEST_Fatal( "Must specify remote identity using --identity-remote" );
// Initialize library, with the desired local identity
TEST_Init( &identityLocal );
// Create the signaling service
SteamNetworkingErrMsg errMsg;
ITrivialSignalingClient *pSignaling = CreateTrivialSignalingClient( pszTrivialSignalingService, SteamNetworkingSockets(), errMsg );
if ( pSignaling == nullptr )
TEST_Fatal( "Failed to initializing signaling client. %s", errMsg );
SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged( OnSteamNetConnectionStatusChanged );
// Create listen socket to receive connections on, unless we are the client
if ( g_eTestRole == k_ETestRole_Server )
{
TEST_Printf( "Creating listen socket, local virtual port %d\n", g_nVirtualPortLocal );
g_hListenSock = SteamNetworkingSockets()->CreateListenSocketP2P( g_nVirtualPortLocal, 0, nullptr );
assert( g_hListenSock != k_HSteamListemSocket_Invalid );
}
else if ( g_eTestRole == k_ETestRole_Symmetric )
{
// Currently you must create a listen socket to use symmetric mode,
// even if you know that you will always create connections "both ways".
// In the future we might try to remove this requirement. It is a bit
// less efficient, since it always triggered the race condition case
// where both sides create their own connections, and then one side
// decides to their theirs away. If we have a listen socket, then
// it can be the case that one peer will receive the incoming connection
// from the other peer, and since he has a listen socket, can save
// the connection, and then implicitly accept it when he initiates his
// own connection. Without the listen socket, if an incoming connection
// request arrives before we have started connecting out, then we are forced
// to ignore it, as the app has given no indication that it desires to
// receive inbound connections at all.
TEST_Printf( "Creating listen socket in symmetric mode, local virtual port %d\n", g_nVirtualPortLocal );
SteamNetworkingConfigValue_t opt;
opt.SetInt32( k_ESteamNetworkingConfig_SymmetricConnect, 1 ); // << Note we set symmetric mode on the listen socket
g_hListenSock = SteamNetworkingSockets()->CreateListenSocketP2P( g_nVirtualPortLocal, 1, &opt );
assert( g_hListenSock != k_HSteamListemSocket_Invalid );
}
// Begin connecting to peer, unless we are the server
if ( g_eTestRole != k_ETestRole_Server )
{
std::vector< SteamNetworkingConfigValue_t > vecOpts;
// If we want the local and virtual port to differ, we must set
// an option. This is a pretty rare use case, and usually not needed.
// The local virtual port is only usually relevant for symmetric
// connections, and then, it almost always matches. Here we are
// just showing in this example code how you could handle this if you
// needed them to differ.
if ( g_nVirtualPortRemote != g_nVirtualPortLocal )
{
SteamNetworkingConfigValue_t opt;
opt.SetInt32( k_ESteamNetworkingConfig_LocalVirtualPort, g_nVirtualPortLocal );
vecOpts.push_back( opt );
}
// Symmetric mode? Noce that since we created a listen socket on this local
// virtual port and tagged it for symmetric connect mode, any connections
// we create that use the same local virtual port will automatically inherit
// this setting. However, this is really not recommended. It is best to be
// explicit.
if ( g_eTestRole == k_ETestRole_Symmetric )
{
SteamNetworkingConfigValue_t opt;
opt.SetInt32( k_ESteamNetworkingConfig_SymmetricConnect, 1 );
vecOpts.push_back( opt );
TEST_Printf( "Connecting to '%s' in symmetric mode, virtual port %d, from local virtual port %d.\n",
SteamNetworkingIdentityRender( identityRemote ).c_str(), g_nVirtualPortRemote,
g_nVirtualPortLocal );
}
else
{
TEST_Printf( "Connecting to '%s', virtual port %d, from local virtual port %d.\n",
SteamNetworkingIdentityRender( identityRemote ).c_str(), g_nVirtualPortRemote,
g_nVirtualPortLocal );
}
g_hConnection = SteamNetworkingSockets()->ConnectP2P( identityRemote, g_nVirtualPortRemote, (int)vecOpts.size(), vecOpts.data() );
}
// Main test loop
for (;;)
{
// Check for incoming signals, and dispatch them
pSignaling->Poll();
// Check callbacks
TEST_PumpCallbacks();
}
TEST_Kill();
return 0;
}
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
# Test script to run the p2p test. This runs three processes: the two clients,
# and the dummy signaling service.
#
# NOTE: You usually won't run this script from its original location. The
# makefiles will copy it into the same location as the tests and examples
import subprocess
import threading
def RunProcess( tag, cmdline, **popen_kwargs ):
with open( tag + ".log", "wt" ) as log:
def WriteLn( ln ):
print( "%s> %s" % (tag, ln ) )
log.write( "%s\n" % ln )
WriteLn( "Executing: " + ' '.join( cmdline ) )
process = subprocess.Popen( cmdline, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, **popen_kwargs )
process.stdin.close()
while True:
sOutput = process.stdout.readline()
if sOutput:
sOutput = str(sOutput, 'utf-8', 'ignore')
WriteLn( sOutput.rstrip() )
elif process.poll() is not None:
break
process.wait()
WriteLn( "Exitted with %d" % process.returncode )
def StartProcessInThread( tag, cmdline, **popen_kwargs ):
def ThreadProc():
RunProcess( tag, cmdline, **popen_kwargs )
thread = threading.Thread( target=ThreadProc, name=tag )
thread.start()
return thread
def StartClientInThread( role, local, remote ):
cmdline = [
"./test_p2p",
"--" + role,
"--identity-local", "str:"+local,
"--identity-remote", "str:"+remote,
"--signaling-server", "localhost:10000"
]
return StartProcessInThread( local, cmdline );
signaling = StartProcessInThread( "signaling", [ './trivial_signaling_server' ], cwd="../examples" )
client1 = StartClientInThread( "server", "peer_server", "peer_client" )
client2 = StartClientInThread( "client", "peer_client", "peer_server" )
client1.join()
client2.join()
signaling.join()