GameNetworkingSockets Documentation

Reliable & unreliable messages over UDP. Robust message fragmentation & reassembly. P2P networking / NAT traversal. Encryption.

RAW Doc

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.
    See ISteamNetworkingSockets::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.
    • ISteamNetworkingMessages is 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.
  • 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.

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:

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 gamenetworkingsockets as a dependency in your project's vcpkg.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:

text
> git clone https://github.com/microsoft/vcpkg
> .\vcpkg\bootstrap-vcpkg.bat

Now 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.

text
> .\vcpkg\vcpkg install --triplet=x64-windows

Next 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.

text
> cmake -S . -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=vcpkg\scripts\buildsystems\vcpkg.cmake

Finally, build the project:

text
> cd build
> ninja

File: 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-sse2

    Also fuzz against ed25519-donna-sse2

  • --with-openssl

    Build with OpenSSL's SHA-512.

    Default: Reference SHA-512 implementation (slow!)

  • --compiler=[gcc,clang,icc]

    Default: gcc

  • --no-asm

    Do not use platform specific assembler

example:

text
php build-nix.php --bits=64 --function=ed25519 --with-sse2 --compiler=icc

Windows

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.c
  • fuzz/ed25519-ref10.c
  • fuzz/ed25519-donna.c
  • fuzz/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):

text
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:

text
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: no

In 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:

text
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

text
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:

text
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

text
gcc ed25519.c -m32 -O3 -c

64-bit

text
gcc ed25519.c -m64 -O3 -c

SSE2

text
gcc ed25519.c -m32 -O3 -c -DED25519_SSE2 -msse2
gcc ed25519.c -m64 -O3 -c -DED25519_SSE2

clang and icc are also supported

Usage

To use the code, link against ed25519.o -mbits and:

text
#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:

text
ed25519_secret_key sk;
randombytes(sk, sizeof(ed25519_secret_key));

To generate a public key:

text
ed25519_public_key pk;
ed25519_publickey(sk, pk);

To sign a message:

text
ed25519_signature sig;
ed25519_sign(message, message_len, sk, pk, signature);

To verify a signature:

text
int valid = ed25519_sign_open(message, message_len, pk, signature) == 0;

To batch verify signatures:

text
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 in
ed25519-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.

text
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

text
gcc curve25519.c -m32 -O3 -c

64-bit

text
gcc curve25519.c -m64 -O3 -c

SSE2

text
gcc curve25519.c -m32 -O3 -c -DCURVE25519_SSE2 -msse2
gcc curve25519.c -m64 -O3 -c -DCURVE25519_SSE2

clang, 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:

text
#include "curve25519.h"

To generate a private/secret key, generate 32 cryptographically random bytes:

text
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:

text
curve25519_key pk;
curve25519_donna_basepoint(pk, sk);

To generate a shared key with your private/secret key and someone elses public key:

text
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

djb's curve25519 paper

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 flag
ENABLE_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:
  • 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.