Hi everyone,
I’m working on a Go library called libknock, and I’d like to get feedback on the API design and overall abstraction.
The project is an embeddable TCP pre-application authentication SDK for Go applications.
The basic idea is:
client
-> TCP connect
-> send a binary authentication frame
-> application protocol starts
server
-> accept TCP
-> verify the authentication frame
-> return a clean net.Conn
-> application protocol starts
The server side exposes authenticated net.Listener wrappers. The client side exposes an authenticated net.Dialer-style API.
A minimal server looks like this:
ln, err := net.Listen("tcp", ":9000")
if err != nil {
return err
}
ln = libknock.WrapListener(ln, knockCfg)
for {
conn, err := ln.Accept()
if err != nil {
return err
}
go handleConn(conn)
}
A minimal client looks like this:
d := libknock.Dialer{
Base: &net.Dialer{},
Config: knockCfg,
}
conn, err := d.DialContext(ctx, "tcp", "example.com:9000")
if err != nil {
return err
}
The library verifies a binary auth frame before the application protocol starts. After successful authentication, the caller receives a normal net.Conn.
The upper layer can be plain TCP, TLS, HTTP, gRPC, a custom binary protocol, or some long-lived agent connection. libknock does not parse or modify the application protocol payload.
The current scope is:
-
authenticated
net.Listener -
authenticated
net.Dialer -
binary auth frame
-
timestamp window checks
-
nonce replay protection
-
client secret resolution
-
clean
net.Connhandoff -
optional port knocking / firewall gate modes
-
optional relay mode for unmodified upstream binaries
It is not intended to replace TLS, mTLS, application authentication, authorization, or a VPN. The goal is only to add a small TCP-level admission step before the application protocol parser sees any input.
The main design questions I’d like feedback on are:
-
Does the
net.Listener/net.Dialerabstraction feel idiomatic for this kind of library? -
Should low-level functions like
ServerAuth(ctx, conn, cfg)andClientAuth(ctx, conn, cfg)be part of the stable public API, or should users be encouraged to use onlyWrapListenerandDialer? -
What is the best way to expose peer metadata after authentication without making the API awkward, especially when the connection is later wrapped by
tls.Conn? -
Should optional port knocking / firewall gate support live in the same repository, or should it be separated from the core TCP pre-auth package?
-
Are there any obvious pitfalls in wrapping
net.Conn/net.Listenerthis way that I should account for before stabilizing the API?
Repository:
https://github.com/libknock/libknock
This is currently an early RC, so I’m more interested in API and design feedback than in promoting it as a stable package.