File: README.md
GameNetworkingSockets
GameNetworkingSockets is a basic transport layer for games. The features are:
- Connection-oriented API (like TCP)
- ... but message-oriented (like UDP), not stream-oriented.
- Supports both reliable and unreliable message types
- Messages can be larger than underlying MTU. The protocol performs
fragmentation, reassembly, and retransmission for reliable messages. - A reliability layer
significantly more sophisticated than a basic TCP-style sliding window.
It is based on the "ack vector" model from DCCP (RFC 4340, section 11.4)
and Google QUIC and discussed in the context of games by
Glenn Fiedler.
The basic idea is for the receiver to efficiently communicate to the sender
the status of every packet number (whether or not a packet was received
with that number). By remembering which segments were sent in each packet,
the sender can deduce which segments need to be retransmitted. - Encryption. AES-GCM-256 per packet, Curve25519 for
key exchange and cert signatures. The details for shared key derivation and
per-packet IV are based on the design
used by Google's QUIC protocol. - Tools for simulating packet latency/loss, and detailed stats measurement
- Head-of-line blocking control and bandwidth sharing of multiple message
streams ("lanes") on the same connection. You can use strict priority
values, softer weight values
that control how bandwidth is shared, or some combination of the two methods.
SeeISteamNetworkingSockets::ConfigureConnectionLanes. - IPv6 support
- Peer-to-peer networking:
- NAT traversal through google WebRTC's ICE implementation.
- Plug in your own signaling service.
- Unique "symmetric connect" mode.
ISteamNetworkingMessagesis an
interface designed to make it easy to port UDP-based code to P2P use cases. (By
UDP-based, we mean non-connection-oriented code, where each time you send a
packet, you specify the recipient's address.)- See README_P2P.md for more info
- Cross platform. This library has shipped on consoles, mobile platforms, and non-Steam
stores, and has been used to facilitate cross-platform connectivity. Contact us to get
access to the code. (We are not allowed to distribute it here.)
What it does not do:
- Higher level serialization of entities, delta encoding of changed state
variables, etc - Compression
Quick API overview
To get an idea of what the API is like, here are a few things to check out:
- The include/steam folder has the public API headers.
ISteamNetworkingSocketsis the
most important interface.steamnetworkingtypes.hhas misc
types and declarations.
- The
Steamworks SDK documentation
offers web-based documentation for these APIs. Note that some features
are only available on Steam, such as Steam's authentication service,
signaling service, and the SDR relay service. - Look at these examples:
- example_chat.cpp. Very simple client/server
program using all reliable messages over ordinary IPv4. - test_p2p.cpp. Shows how to get two hosts to connect
to each other using P2P connectivity. Also an example of how to write a
signaling service plugin.
- example_chat.cpp. Very simple client/server
Building
See BUILDING for more information.
Language bindings
The library was written in C++, but there is also a plain C interface
to facilitate binding to other languages.
Third party language bindings:
- C#:
- Go:
- Rust:
Why do I see "Steam" everywhere?
The main interface class is named SteamNetworkingSockets, and many files have
"steam" in their name. But Steam is not needed. If you don't make games or
aren't on Steam, feel free to use this code for whatever purpose you want.
The reason for "Steam" in the names is that this provides a subset of the
functionality of the API
with the same name in the Steamworks SDK. Our main
reason for releasing this code is so that developers won't have any hesitation
coding to the API in the Steamworks SDK. On Steam, you will link against the
Steamworks version, and you can access the additional services provided by
the Steam Datagram Relay
network. On other platforms and stores, as long as you ship a version of your
game on Steam, you might be able to take advantage of these services. See
the Steamworks documentation for more information. Because this is a live
service, and we need to control our security and backward compatibility burden,
at this time we are not able to offer access to SDR on other platforms to all
partners.
If you aren't a Steam partner, or don't have a version of your game on Steam,
then use this opensource version of the API and take advantage of the permissive
license to do whatever you want. We want you to take maximum advantage of the
features in the Steamworks version. That won't happen if this API is a weird
"wart" that's hidden behind #ifdef STEAM, which is why we're making this
opensource version available.
The desire to match the Steamworks SDK also explains a somewhat anachronistic
coding style and weird directory layout. This project is kept in sync with the
Steam code here at Valve. When we extracted the code from the much larger
codebase, we had to do some relatively gross hackery. The files in folders
named tier0, tier1, vstdlib, common, etc have especially suffered
trauma. Also if you see code that appears to have unnecessary layers of
abstraction, it's probably because those layers are needed to support relayed
connection types or some part of the Steamworks SDK.
Security
Did you find a security vulnerability? Please inform us responsibly; you may
be eligible for a bug bounty. See the security policy for more
information.
File: examples/vcpkg_example_chat/README.md
The purpose of this project is to demonstrate/test a project that pulls in
GameNetworkingSockets and its dependencies entirely using vcpkg.
TL;DR
We assume that your project defines its dependences in a vcpkg.json file, and
you are pulling in vcpkg's cmake toolchain file. Then all you need to do is:
- Add
gamenetworkingsocketsas a dependency in your project'svcpkg.json - In your cmake file, add
find_package( GameNetworkingSockets ) - Link your targets with the appropriate library:
target_link_libraries(<your_target> GameNetworkingSockets::shared)- or
target_link_libraries(<your_target> GameNetworkingSockets::static)
Building the example project using vcpkg in manifest mode
Here are some specific instructions that build the example project using vcpkg in
"manifest mode".
This example assumes Windows, and also it's recommended to run this from a
Visual Studio command prompt so we know what compiler to use.
First, we bootstrap a project-specific installation of vcpkg ("manifest mode")
in the default location, /vcpkg. From the project root, run these
commands:
> git clone https://github.com/microsoft/vcpkg
> .\vcpkg\bootstrap-vcpkg.batNow we ask vcpkg to install of the dependencies for our project, which are described by
the file /vcpkg.json. Note that this step is optional, as cmake will
automatically do this. But here we are doing it in a separate step so that we can isolate
any problems, because if problems happen here don't have anything to do with your
cmake files.
> .\vcpkg\vcpkg install --triplet=x64-windowsNext build the project files. There are different options for telling cmake how
to integrate with vcpkg; here we use CMAKE_TOOLCHAIN_FILE on the command line.
Also we select Ninja project generator.
> cmake -S . -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=vcpkg\scripts\buildsystems\vcpkg.cmakeFinally, build the project:
> cd build
> ninjaFile: src/external/ed25519-donna/fuzz/README.md
This code fuzzes ed25519-donna (and optionally ed25519-donna-sse2) against the ref10 implementations of
curve25519 and
ed25519.
Curve25519 tests that generating a public key from a secret key
Building
*nix + PHP
php build-nix.php (required parameters) (optional parameters)
Required parameters:
--function=[curve25519,ed25519]--bits=[32,64]
Optional parameters:
--with-sse2Also fuzz against ed25519-donna-sse2
--with-opensslBuild with OpenSSL's SHA-512.
Default: Reference SHA-512 implementation (slow!)
--compiler=[gcc,clang,icc]Default: gcc
--no-asmDo not use platform specific assembler
example:
php build-nix.php --bits=64 --function=ed25519 --with-sse2 --compiler=iccWindows
Create a project with access to the ed25519 files.
If you are not using OpenSSL, add the ED25519_REFHASH define to the projects
"Properties/Preprocessor/Preprocessor Definitions" option
Add the following files to the project:
fuzz/curve25519-ref10.cfuzz/ed25519-ref10.cfuzz/ed25519-donna.cfuzz/ed25519-donna-sse2.c(optional)fuzz-[curve25519/ed25519].c(depending on which you want to fuzz)
If you are also fuzzing against ed25519-donna-sse2, add the ED25519_SSE2 define for fuzz-[curve25519/ed25519].c under
its "Properties/Preprocessor/Preprocessor Definitions" option.
Running
If everything agrees, the program will only output occasional status dots (every 0x1000 passes)
and a 64bit progress count (every 0x20000 passes):
fuzzing: ref10 curved25519 curved25519-sse2
................................ [0000000000020000]
................................ [0000000000040000]
................................ [0000000000060000]
................................ [0000000000080000]
................................ [00000000000a0000]
................................ [00000000000c0000]If any of the implementations do not agree with the ref10 implementation, the program will dump
the random data that was used, the data generated by the ref10 implementation, and diffs of the
ed25519-donna data against the ref10 data.
Example errors
These are example error dumps (with intentionally introduced errors).
Ed25519
Random data:
- sk, or Secret Key
- m, or Message
Generated data:
- pk, or Public Key
- sig, or Signature
- valid, or if the signature of the message is valid with the public key
Dump:
sk:
0x3b,0xb7,0x17,0x7a,0x66,0xdc,0xb7,0x9a,0x90,0x25,0x07,0x99,0x96,0xf3,0x92,0xef,
0x78,0xf8,0xad,0x6c,0x35,0x87,0x81,0x67,0x03,0xe6,0x95,0xba,0x06,0x18,0x7c,0x9c,
m:
0x7c,0x8d,0x3d,0xe1,0x92,0xee,0x7a,0xb8,0x4d,0xc9,0xfb,0x02,0x34,0x1e,0x5a,0x91,
0xee,0x01,0xa6,0xb8,0xab,0x37,0x3f,0x3d,0x6d,0xa2,0x47,0xe3,0x27,0x93,0x7c,0xb7,
0x77,0x07,0xb6,0x88,0x41,0x22,0xf3,0x3f,0xce,0xcb,0x6b,0x3e,0x2b,0x23,0x68,0x7f,
0x5b,0xb9,0xda,0x04,0xbb,0xae,0x42,0x50,0xf5,0xe9,0xc5,0x11,0xbd,0x52,0x76,0x98,
0xf1,0x87,0x09,0xb9,0x89,0x0a,0x52,0x69,0x01,0xce,0xe0,0x4a,0xa6,0x46,0x5a,0xe1,
0x63,0x14,0xe0,0x81,0x52,0xec,0xcd,0xcf,0x70,0x54,0x7d,0xa3,0x49,0x8b,0xf0,0x89,
0x70,0x07,0x12,0x2a,0xd9,0xaa,0x16,0x01,0xb2,0x16,0x3a,0xbb,0xfc,0xfa,0x13,0x5b,
0x69,0x83,0x92,0x70,0x95,0x76,0xa0,0x8e,0x16,0x79,0xcc,0xaa,0xb5,0x7c,0xf8,0x7a,
ref10:
pk:
0x71,0xb0,0x5e,0x62,0x1b,0xe3,0xe7,0x36,0x91,0x8b,0xc0,0x13,0x36,0x0c,0xc9,0x04,
0x16,0xf5,0xff,0x48,0x0c,0x83,0x6b,0x88,0x53,0xa2,0xc6,0x0f,0xf7,0xac,0x42,0x04,
sig:
0x3e,0x05,0xc5,0x37,0x16,0x0b,0x29,0x30,0x89,0xa3,0xe7,0x83,0x08,0x16,0xdd,0x96,
0x02,0xfa,0x0d,0x44,0x2c,0x43,0xaa,0x80,0x93,0x04,0x58,0x22,0x09,0xbf,0x11,0xa5,
0xcc,0xa5,0x3c,0x9f,0xa0,0xa4,0x64,0x5a,0x4a,0xdb,0x20,0xfb,0xc7,0x9b,0xfd,0x3f,
0x08,0xae,0xc4,0x3c,0x1e,0xd8,0xb6,0xb4,0xd2,0x6d,0x80,0x92,0xcb,0x71,0xf3,0x02,
valid: yes
ed25519-donna:
pk diff:
____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,
____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,
sig diff:
0x2c,0xb9,0x25,0x14,0xd0,0x94,0xeb,0xfe,0x46,0x02,0xc2,0xe8,0xa3,0xeb,0xbf,0xb5,
0x72,0x84,0xbf,0xc1,0x8a,0x32,0x30,0x99,0xf7,0x58,0xfe,0x06,0xa8,0xdc,0xdc,0xab,
0xb5,0x57,0x03,0x33,0x87,0xce,0x54,0x55,0x6a,0x69,0x8a,0xc4,0xb7,0x2a,0xed,0x97,
0xb4,0x68,0xe7,0x52,0x7a,0x07,0x55,0x3b,0xa2,0x94,0xd6,0x5e,0xa1,0x61,0x80,0x08,
valid: noIn this case, the generated public key matches, but the generated signature is completely
different and does not validate.
Curve25519
Random data:
- sk, or Secret Key
Generated data:
- pk, or Public Key
Dump:
sk:
0x44,0xec,0x0b,0x0e,0xa2,0x0e,0x9c,0x5b,0x8c,0xce,0x7b,0x1d,0x68,0xae,0x0f,0x9e,
0x81,0xe2,0x04,0x76,0xda,0x87,0xa4,0x9e,0xc9,0x4f,0x3b,0xf9,0xc3,0x89,0x63,0x70,
ref10:
0x24,0x55,0x55,0xc0,0xf9,0x80,0xaf,0x02,0x43,0xee,0x8c,0x7f,0xc1,0xad,0x90,0x95,
0x57,0x91,0x14,0x2e,0xf2,0x14,0x22,0x80,0xdd,0x4e,0x3c,0x85,0x71,0x84,0x8c,0x62,
curved25519 diff:
0x12,0xd1,0x61,0x2b,0x16,0xb3,0xd8,0x29,0xf8,0xa3,0xba,0x70,0x4e,0x49,0x4f,0x43,
0xa1,0x3c,0x6b,0x42,0x11,0x61,0xcc,0x30,0x87,0x73,0x46,0xfb,0x85,0xc7,0x9a,0x35,
curved25519-sse2 diff:
____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,
____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,____,In this case, curved25519 is totally wrong, while curved25519-sse2 matches the reference
implementation.
File: src/external/ed25519-donna/README.md
ed25519 is an
Elliptic Curve Digital Signature Algortithm,
developed by Dan Bernstein,
Niels Duif,
Tanja Lange,
Peter Schwabe,
and Bo-Yin Yang.
This project provides performant, portable 32-bit & 64-bit implementations. All implementations are
of course constant time in regard to secret data.
Performance
SSE2 code and benches have not been updated yet. I will do those next.
Compilers versions are gcc 4.6.3, icc 13.1.1, clang 3.4-1~exp1.
Batch verfication time (in parentheses) is the average time per 1 verification in a batch of 64 signatures. Counts are in thousands of cycles.
Note that SSE2 performance may be less impressive on AMD & older CPUs with slower SSE ops!
Visual Studio performance for ge25519_scalarmult_base_niels will lag behind a bit until optimized assembler versions of ge25519_scalarmult_base_choose_niels
are made.
E5200 @ 2.5ghz, march=core2
| Implementation | Sign | gcc | icc | clang | Verify | gcc | icc | clang |
|---|---|---|---|---|---|---|---|---|
| ed25519-donna 64bit | 100k | 110k | 137k | 327k (144k) | 342k (163k) | 422k (194k) | ||
| amd64-64-24k | 102k | 355k (158k) | ||||||
| ed25519-donna-sse2 64bit | 108k | 111k | 116k | 353k (155k) | 345k (154k) | 360k (161k) | ||
| amd64-51-32k | 116k | 380k (175k) | ||||||
| ed25519-donna-sse2 32bit | 147k | 147k | 156k | 380k (178k) | 381k (173k) | 430k (192k) | ||
| ed25519-donna 32bit | 597k | 335k | 380k | 1693k (720k) | 1052k (453k) | 1141k (493k) |
E3-1270 @ 3.4ghz, march=corei7-avx
| Implementation | Sign | gcc | icc | clang | Verify | gcc | icc | clang |
|---|---|---|---|---|---|---|---|---|
| amd64-64-24k | 68k | 225k (104k) | ||||||
| ed25519-donna 64bit | 71k | 75k | 90k | 226k (105k) | 226k (112k) | 277k (125k) | ||
| amd64-51-32k | 72k | 218k (107k) | ||||||
| ed25519-donna-sse2 64bit | 79k | 82k | 92k | 252k (122k) | 259k (124k) | 282k (131k) | ||
| ed25519-donna-sse2 32bit | 94k | 95k | 103k | 296k (146k) | 294k (137k) | 306k (147k) | ||
| ed25519-donna 32bit | 525k | 299k | 316k | 1502k (645k) | 959k (418k) | 954k (416k) |
Compilation
No configuration is needed if you are compiling against OpenSSL.
Hash Options
If you are not compiling aginst OpenSSL, you will need a hash function.
To use a simple/slow implementation of SHA-512, use -DED25519_REFHASH when compiling ed25519.c.
This should never be used except to verify the code works when OpenSSL is not available.
To use a custom hash function, use -DED25519_CUSTOMHASH when compiling ed25519.c and put your
custom hash implementation in ed25519-hash-custom.h. The hash must have a 512bit digest and implement
struct ed25519_hash_context;
void ed25519_hash_init(ed25519_hash_context *ctx);
void ed25519_hash_update(ed25519_hash_context *ctx, const uint8_t *in, size_t inlen);
void ed25519_hash_final(ed25519_hash_context *ctx, uint8_t *hash);
void ed25519_hash(uint8_t *hash, const uint8_t *in, size_t inlen);Random Options
If you are not compiling aginst OpenSSL, you will need a random function for batch verification.
To use a custom random function, use -DED25519_CUSTOMRANDOM when compiling ed25519.c and put your
custom hash implementation in ed25519-randombytes-custom.h. The random function must implement:
void ED25519_FN(ed25519_randombytes_unsafe) (void *p, size_t len);Use -DED25519_TEST when compiling ed25519.c to use a deterministically seeded, non-thread safe CSPRNG
variant of Bob Jenkins ISAAC
Minor options
Use -DED25519_INLINE_ASM to disable the use of custom assembler routines and instead rely on portable C.
Use -DED25519_FORCE_32BIT to force the use of 32 bit routines even when compiling for 64 bit.
32-bit
gcc ed25519.c -m32 -O3 -c64-bit
gcc ed25519.c -m64 -O3 -cSSE2
gcc ed25519.c -m32 -O3 -c -DED25519_SSE2 -msse2
gcc ed25519.c -m64 -O3 -c -DED25519_SSE2clang and icc are also supported
Usage
To use the code, link against ed25519.o -mbits and:
#include "ed25519.h"Add -lssl -lcrypto when using OpenSSL (Some systems don't need -lcrypto? It might be trial and error).
To generate a private key, simply generate 32 bytes from a secure
cryptographic source:
ed25519_secret_key sk;
randombytes(sk, sizeof(ed25519_secret_key));To generate a public key:
ed25519_public_key pk;
ed25519_publickey(sk, pk);To sign a message:
ed25519_signature sig;
ed25519_sign(message, message_len, sk, pk, signature);To verify a signature:
int valid = ed25519_sign_open(message, message_len, pk, signature) == 0;To batch verify signatures:
const unsigned char *mp[num] = {message1, message2..}
size_t ml[num] = {message_len1, message_len2..}
const unsigned char *pkp[num] = {pk1, pk2..}
const unsigned char *sigp[num] = {signature1, signature2..}
int valid[num]
/* valid[i] will be set to 1 if the individual signature was valid, 0 otherwise */
int all_valid = ed25519_sign_open_batch(mp, ml, pkp, sigp, num, valid) == 0;Note: Batch verification uses ed25519_randombytes_unsafe, implemented ined25519-randombytes.h, to generate random scalars for the verification code.
The default implementation now uses OpenSSLs RAND_bytes.
Unlike the SUPERCOP version, signatures are
not appended to messages, and there is no need for padding in front of messages.
Additionally, the secret key does not contain a copy of the public key, so it is
32 bytes instead of 64 bytes, and the public key must be provided to the signing
function.
Curve25519
Curve25519 public keys can be generated thanks to
Adam Langley
leveraging Ed25519's precomputed basepoint scalar multiplication.
curved25519_key sk, pk;
randombytes(sk, sizeof(curved25519_key));
curved25519_scalarmult_basepoint(pk, sk);Note the name is curved25519, a combination of curve and ed25519, to prevent
name clashes. Performance is slightly faster than short message ed25519
signing due to both using the same code for the scalar multiply.
Testing
Fuzzing against reference implemenations is now available. See fuzz/README.
Building ed25519.c with -DED25519_TEST and linking with test.c will run basic sanity tests
and benchmark each function. test-batch.c has been incorporated in to test.c.
test-internals.c is standalone and built the same way as ed25519.c. It tests the math primitives
with extreme values to ensure they function correctly. SSE2 is now supported.
Papers
Available on the Ed25519 website
File: src/external/curve25519-donna/README.md
curve25519 is an elliptic curve, developed by
Dan Bernstein, for fast
Diffie-Hellman key agreement.
DJB's original implementation was written in a
language of his own devising called qhasm.
The original qhasm source isn't available, only the x86 32-bit assembly output.
This project provides performant, portable 32-bit & 64-bit implementations.
All implementations are of course constant time in regard to secret data.
Performance
Compilers versions are gcc 4.6.3, icc 13.1.1, clang 3.4-1~exp1.
Counts are in thousands of cycles.
Note that SSE2 performance may be less impressive on AMD & older CPUs with slower SSE ops!
E5200 @ 2.5ghz, march=core2
| Version | gcc | icc | clang |
|---|---|---|---|
| 64-bit SSE2 | 278k | 265k | 302k |
| 64-bit | 273k | 271k | 377k |
| 32-bit SSE2 | 304k | 289k | 317k |
| 32-bit | 1417k | 845k | 981k |
E3-1270 @ 3.4ghz, march=corei7-avx
| Version | gcc | icc | clang |
|---|---|---|---|
| 64-bit | 201k | 192k | 233k |
| 64-bit SSE2 | 201k | 201k | 261k |
| 32-bit SSE2 | 238k | 225k | 250k |
| 32-bit | 1293k | 822k | 848k |
Compilation
No configuration is needed.
32-bit
gcc curve25519.c -m32 -O3 -c64-bit
gcc curve25519.c -m64 -O3 -cSSE2
gcc curve25519.c -m32 -O3 -c -DCURVE25519_SSE2 -msse2
gcc curve25519.c -m64 -O3 -c -DCURVE25519_SSE2clang, icc, and msvc are also supported
Named Versions
Define CURVE25519_SUFFIX to append a suffix to public functions, e.g.-DCURVE25519_SUFFIX=_sse2 to create curve25519_donna_sse2 and
curve25519_donna_basepoint_sse2.
Usage
To use the code, link against curve25519.o and:
#include "curve25519.h"To generate a private/secret key, generate 32 cryptographically random bytes:
curve25519_key sk;
randombytes(sk, sizeof(curve25519_key));Manual clamping is not needed, and it is actually not possible to use unclamped
keys due to the code taking advantage of the clamped bits internally.
To generate the public key from the private/secret key:
curve25519_key pk;
curve25519_donna_basepoint(pk, sk);To generate a shared key with your private/secret key and someone elses public key:
curve25519_key shared;
curve25519_donna(shared, mysk, yourpk);And hash shared with a cryptographic hash before using, or e.g. pass shared through
HSalsa20/HChacha as NaCl does.
Testing
Fuzzing against a reference implemenation is now available. See fuzz/README.
Building curve25519.c and linking with test.c will run basic sanity tests and benchmark curve25519_donna.
Papers
License
Public Domain, or MIT
File: README_P2P.md
About that P2P support....
SteamNetworkingSockets supports peer-to-peer connections. A "peer-to-peer"
connection in this context means that the hosts do not (initially) know
each other's IP address. Furthermore, they may be behind NAT, and so they
may not know their own public IP address. They may not even have a public
IP that other hosts can use to send them inbound traffic. Here
is a good article about the problem of NAT traversal.
ICE
is an internet standard protocol for discovering and sharing IP addresses,
negotiating NAT, and establishing a direct connection or fallback to relaying
the connection if necessary.
The library includes an ICE client implementation with no external dependencies
that is compiled in by default. (See the vcpkg feature ice or the cmake flagENABLE_ICE).
Alternatively, the library can also be compiled to use
Google WebRTC's ICE implementation.
We interface with the WebRTC code at a relatively low level and only use it
for datagram transport. We don't use DTLS or WebRTC data channels.
See also k_ESteamNetworkingConfig_P2P_Transport_ICE_Implementation.
The two candidates are compatible with one another. (A peer using the WebRTC
ICE client can conneect to a peer using the native client.)
Symmetric connect mode
SteamNetworkingSockets offers a unique feature known as symmetric
connect mode, which really puts the "peer" in "peer-to-peer connection".
This feature is useful in the following common use case:
- Two hosts wish to establish a single connection between them.
- Either host may initiate the connection.
- The hosts may initiate the connection at the same time.
This situation involves race conditions that can be tricky to get
right, especially when authentication, encryption, ICE roles, etc
are involved. With symmetric connect mode, sorting out these race
conditions and producing a single connection is handled by the API.
See the k_ESteamNetworkingConfig_SymmetricConnect
connection flag in steamnetworkingtypes.h
for more info.
ISteamNetworkingMessages
Most P2P libraries, such as google WebRTC, and indeed our own
ISteamNetworkingSockets, are connection
oriented. To talk to a peer, you first establish a connection to the peer, and
when you send and receive messages, the peer is identified by the connection handle.
Much existing network code is based on UDP with a single socket, where
connection handles are not used. Instead, packets are sent with the IP address
of the recipeient specified for each packet. (E.g. sentto() and recvfrom()).
ISteamNetworkingMessages was created
to provide a more "ad-hoc" interface like UDP. It can be useful when adding P2P
support to existing code, depending on the abstraction you are working with. If
the code you are modifing already has the concept of a connection, then you might
find it easier to use ISteamNetworkinSockets directly. But if you are modifying code
at a lower level, you may find that you need to maintain a table of active connections,
and each time you send a packet, use the existing connection if one exists, or
create a new connection if one does not exist. This is exactly what
ISteamNetworkingMessages does for you. It creates symmetric-mode connections on
demand the first time you communicate with a given peer, and idle connections are
automatically closed when they are no longer needed.
Requirements
Peer-to-peer connections require more than just working code. In addition
to the code in this library, there are several other prerequisites.
(Note that the Steamworks API provides all of these services for Steam games.)
Signaling service
A side channel, capable of relaying small rendezvous
messages from one host to another. This means hosts must have a constant
connection to your service, once that enables you to push messages to them.
SteamNetworkingSockets supports a pluggable signaling service. The requirements
placed on your signaling service are relatively minimal:
- Individual rendezvous messages are small. (Perhaps bigger than IP MTU,
but never more than a few KB.) - Only datagram "best-effort" delivery is required. The signaling protocol
is tolerant of dropped, duplicated, or reordered messages.
These anomalies may cause negotiation to take longer, but not fail.
This means, for example that there doesn't need to be a mechanism to
inform the system when your connection to the signaling service is
disrupted. - The channel can be relatively low bandwidth and high latency. Usually
there is a burst of a handful of exchanges when a connection is created,
followed by silence, unless something changes with the connection.
STUN server(s)
A STUN server is used to help peers
discover their own public IP address and open up firewalls. STUN
servers are relatively low bandwidth, and there are publicly-available ones.
Relay fallback
Unfortunatley, for some pairs of hosts, NAT piercing is not successful.
In this situation, the traffic must be relayed. In the ICE protocol, this is
done using TURN
servers. (NOTE: Any TURN server also doubles as a STUN server.) Because the TURN
server is relaying every packet, is is a relatively costly service, so you probably
will need to run your own, or just fail connections that cannot pierce NAT.
On Steam we use a custom relay service known as Steam Datgaram Relay
-- SDR for short -- carrying packets through our network of relays and
on our backbone. (You may see this mentioned in the opensource code here,
but the SDR support code is not opensource.) Also, on Steam we often
do not share public IP addresses between untrusted peers, so that malicious
players cannot DoS attack. In that case NAT punch is not possible and traffic
would be relayed.
Naming hosts and matchmaking
The above requirements are just what is needed to make a connection between two
hosts, once you know who to connect to. But before that, you need a way to assign an
identity to a host, authenticate them, matchmaking them, etc. Those services are
also included with Steam, but outside the scope of a transport library like this.
Using P2P
Assuming you have all of those requirements, you can use SteamNetworkingSockets
to make P2P connections!
To enable P2P/ICE support, just use the vcpkg feature ice, or build with the cmake
flag -DENABLE_ICE=ON. Note that both of these are the default, so you just need
to avoid explicitly turning it off.
If you want to use the Google WebRTC ICE implementation, set-DUSE_STEAMWEBRTC=ON. cmake will automatically activate two git submodules
and pull down the Google WebRTC code.
Take a look at these files for more information:
- steamnetworkingcustomsignaling.h
contains the interfaces you'll need to implement for your signaling service. - An example of a really trivial signaling protocol:
- trivial_signaling_server.py server
- trivial_signaling_client.cpp client
- A test case that puts everything together. It starts up an example trivial
signaling protocol server and two peers, and has them connect to each other
and exchange a few messages. We use the publicly-available google STUN servers.
(No TURN servers for relay fallback in this example.)- test_p2p.py Executive test script that starts all the processes.
- test_p2p.cpp Code for the process each peer runs.
Roadmap
Here are some things we have in mind to make P2P support better:
- Get plugins written for standard, opensource protocols that could be used for
signaling, such as XMPP. This would be a great project for
somebody else to do! We would welcome contributions to this repository, or
happily link to your own project. (See issue #136) - LAN beacon support, so that P2P connections can be made even when signaling
is down or the hosts do not have Internet connectivity. (See issue #82) - Support for PCP as an additional
NAT traversal mechanism. - Shared candidate pool across multiple P2P connections. Currently, each
connection goes through a full ICE gathering process independently, including
opening its own sockets and creating its own TURN allocation. A server forming
connections with many peers (e.g. a full mesh) could instead gather candidates
once per interface, share a single set of sockets, and reuse a single TURN
allocation across all peers. This would be a significant efficiency improvement. - TURN channels, for more efficient relay of data packets once a relayed path
is selected. - MTU probing: ICE connectivity checks should be sent at the full path MTU, so
that MTU issues are discovered during negotiation rather than after the
connection is established.