Skip to content
This repository was archived by the owner on Jun 21, 2026. It is now read-only.

Schwungus/NutPunch

Repository files navigation

SUPERSEDED BY NutBlast

This repository and the public server(s) will no longer be maintained. Please check out NutBlast instead, it's more reliable and also supports Emscripten.

NutPunch

A cracked-peanut sign.

Caution

NutPunch implements UDP-based peer-to-peer networking. Client-server architecture is a lot more commonplace in games, and arguably much easier to implement and understand. Use NutPunch only if you know what you're getting yourself into. You have been warned.

NutPunch is a header-only UDP hole-punching library written in plain C. Dependency-free and brutal.

Comes with a public instance for out-of-the-box integration.

✔️ Schwungus-certified.

Troubleshooting

Note

For simplicity's sake, NutPunch does not implement TURN at the moment.

If you're having connectivity issues in a game powered by NutPunch, please make sure (1) you aren't mangling UDP packets (disable zapret) and (2) there is a direct route to your computer from the public net. Using a proxy service for accessing the Web shouldn't interfere as long as you aren't routing your game through it.

You can set up your VPN client to ignore NutPunch-powered games rather than route them through the target proxy server. For example, in AmneziaVPN, use the split tunneling feature to exclude the game's binary from VPN routing. Just follow this infographic from their split-tunneling docs:

An infographic telling you to how to enable per-app split-tunnelling.

Introductory Lecture

This library implements P2P networking, where each peer communicates with all others. It's a complex model, and it could be counterproductive to use if you don't know what you're signing yourself up for. If you don't feel like reading the immediately following blanket of words and scribbles, you may skip to using premade integrations.

Before you can punch any holes in your peers' NAT, you will need a hole-punching server with a public IPv4 address assigned. Querying a public server lets us bust a gateway open from your computer to the public network, which, by definition of hole-punching, is then leaked to other peers for them to be able to establish a direct connection to you. You're free to use our public instance to do the leaking, but hosting your own instance is also possible.

The current server implementation uses a lobby-based approach, where each lobby supports up to 8 peers and is identified by a unique ASCII string. The complete example might be overwhelming at first, but make sure to skim through it before you do any heavy networking. Here's the general usage guide for the NutPunch library:

  1. At the start of the program, set your game ID using NutPunch_SetGameId("game-id"). It is optional but highly recommended, as it allows distinguishing your game's lobbies from others'. A common example of a game ID would be "GameName v1.0.0".
  2. Host a lobby with NutPunch_Host("lobby-name"), or join an existing one with NutPunch_Join("lobby-name"). Lobby names and lobby IDs are one and the same even if different wording is used. This also means you cannot have two different lobbies with identical names; the lobby's name identifies it uniquely per game ID.
  3. Listen for events:
    1. Call NutPunch_Update() each frame. This will also automatically send lobby and peer metadata back and forth to the NutPuncher server.
    2. Check your status by matching the returned value against NPS_* constants. NPS_Online is what you're looking for normally, but make sure to handle NPS_Error. To get a human-readable error description, call NutPunch_GetLastError().
  4. Run the game logic.
  5. Keep in sync with the peers:
    1. Send messages with NutPunch_Send() or NutPunch_SendReliably() (for reliable delivery).
    2. Poll for incoming messages with NutPunch_HasMessage() and retrieve them with NutPunch_NextMessage().
    3. Set/retrieve lobby or peer metadata with NutPunch_Set*Data()/NutPunch_Get*Data().
    4. At this point you might have to call NutPunch_Flush() to flush the outgoing packet queue to prevent 1-frame delays between updating the gamestate locally and sending those updates over the network.
  6. Repeat steps 3 through 5 throughout the networking session.
  7. Use NutPunch_Disconnect() to leave the lobby, or NutPunch_Shutdown() at the end of your program to perform final cleanup. (The latter disconnects you from the lobby as well.) You're all set!

An important aspect of NutPunch networking is the ability to set lobby/peer metadata in a simplified key-value-store fashion. Peer metadata can include e.g. the peer's username, their character - anything you can squeeze into a 32-byte null-terminated string, mapped to 16-byte null-terminated string key. The same applies to lobby metadata: this could be the name of the level to play on, the difficulty level, rules to alter the game's behavior, etc.

For the lobby and each peer, the current limit to how many fields they can hold is 8.

Call NutPunch_Set*Data(...)/NutPunch_Get*Data(...) to set/get key-value pairs; replace the asterisk with either Peer or Lobby. Setting metadata only does anything if you're "in charge" of the metadata object: either you're the lobby's master and want to set the lobby's metadata, or you're trying to set your own metadata as a peer.

Premade Integrations

None yet.

TODO: Add a GekkoNet network adapter implementation.

Installation

If you're using CMake, you can include this library in your project by adding the following to your CMakeLists.txt:

include(FetchContent)
FetchContent_Declare(NutPunch
    GIT_REPOSITORY https://github.com/Schwungus/nutpunch.git
    GIT_TAG master) # you can use a specific commit hash here
FetchContent_MakeAvailable(NutPunch)

add_executable(MyGame main.c) # your game's CMake target goes here
target_link_libraries(MyGame PRIVATE NutPunch)

For other build systems (or lack thereof), you only need to copy NutPunch.h into your include path. Make sure to link against ws2_32 on Windows though, or else you'll end up with scary linker errors related to Winsock.

Basic Usage

Once NutPunch.h is in your include-path, using it is straightforward, just like any header-only library. Select a source file where the library's function definitions will reside (it could be your main.c as well), tell the compiler to add NutPunch implementation details with a #define NUTPUNCH_IMPLEMENTATION, and #include the library's main header inside it:

#include <stdlib.h> // for EXIT_SUCCESS

#define NUTPUNCH_IMPLEMENTATION
#include <NutPunch.h>

int main(int argc, char* argv[]) {
    (void)argc, (void)argv;

    NutPunch_SetGameId("My Cool Game");
    NutPunch_Join("My Lobby");

    for (;;) { // your game's mainloop goes here...
        NutPunch_Update();
        Sleep(1000 / 60);
    }

    NutPunch_Shutdown();
    return EXIT_SUCCESS;
}

If you want to see all the juicy APIs in action, read up on Test.c from this repo. For a general overview of available functionality, just read the doc-comments for the functions around the middle of NutPunch.h. Also take a look at the advanced usage section to discover things you can customize.

Public Instance

If you don't feel like hosting your own instance, you may use our public instance. It's used by default unless a different server is specified.

If you want to be explicit about using the public instance, call NutPunch_SetServerAddr:

NutPunch_SetServerAddr(NUTPUNCH_DEFAULT_SERVER);
NutPunch_Join("lobby-id");

Advanced Usage

Replace Stdlib Dependencies

NutPunch relies heavily on the C standard library. If you have a need or desire to cut that off from your project, just #define replacements NutPunch can use in its implementation.

Please note that you can't customize the APIs used for networking (yet). We're interfacing directly with raw winsock & BSD sockets; if none are available, your platform isn't supported.

Either way, here's a no-stdlib SDL3 example:

#include <SDL3/SDL_stdinc.h>

#define NUTPUNCH_IMPLEMENTATION
#define NUTPUNCH_NOSTD

#define NutPunch_SNPrintF SDL_snprintf
#define NutPunch_StrNCmp SDL_strncmp
#define NutPunch_StrNLen SDL_strnlen
#define NutPunch_MemCmp SDL_memcmp
#define NutPunch_MemSet SDL_memset
#define NutPunch_MemCpy SDL_memcpy
#define NutPunch_Malloc SDL_malloc
#define NutPunch_Free SDL_free
#define NutPunch_TimeNS SDL_GetTicksNS
// NOTE: this still pulls in <stdio.h> unless you customize the logger as well (see the section below).

#include <NutPunch.h>

Customize Logger Implementation

Just like in the example above, you can override NutPunch's logging facility before including NutPunch.h:

#include <stdio.h>

// override with a simplified version of the default logger:
#define NutPunch_Log(msg, ...) printf(msg "\n", ##__VA_ARGS__)

#define NUTPUNCH_IMPLEMENTATION
#include <NutPunch.h>

Hosting your own NutPuncher

If you're dissatisfied with the public instance, whether from needing to stick to a specific build or fork or whatever, you can easily host your own. Make sure to read the introductory pamphlet before attempting this.

In order to run your own hole-punching server, you'll need to obtain a server binary, usually by building NutPuncher from sources. To do that, make sure you have CMake and a C/C++ compiler installed. For example, on an Ubuntu/Debian host, run:

sudo apt update
sudo apt install --yes git cmake make g++

Assuming you're hosting the NutPuncher on the Linux machine you're building it on, here's a TL;DR of what commands you need to execute:

git clone https://github.com/Schwungus/NutPunch
cd NutPunch
cmake -S . -B build -D NUTPUNCH_BUILD_SERVER=ON
cmake --build build

Then run the NutPuncher binary as you would usually do:

./build/NutPuncher

If you wish to keep the NutPuncher running between system restarts and internal errors/crashes, check out the systemd service we're using ourselves as reference. But in short, you'll have to write the following in your ~/.config/systemd/user/NutPuncher.service:

[Unit]
Description=NutPuncher
After=network.target

[Service]
ExecStart=%h/NutPunch/build/NutPuncher
Restart=always

[Install]
WantedBy=default.target

And then enable and run this service by executing:

systemctl --user enable --now NutPuncher

About

* SUPERSEDED BY NutBlast: https://nutblast.schwung.us * Header-only UDP hole-punching for plain C.

Topics

Resources

License

Stars

3 stars

Watchers

1 watching

Forks

Contributors