{"id":37957499,"url":"https://github.com/theredrad/udpsocket","last_synced_at":"2026-01-16T18:01:27.800Z","repository":{"id":47615852,"uuid":"392645417","full_name":"theredrad/udpsocket","owner":"theredrad","description":"A simple UDP server to make a virtual secure channel with the clients","archived":false,"fork":false,"pushed_at":"2022-03-23T21:01:29.000Z","size":89,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-06-19T11:37:34.739Z","etag":null,"topics":["encryption","end-to-end-encryption","game-server","go","golang","udp","udp-server","udp-socket"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theredrad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-08-04T10:26:30.000Z","updated_at":"2024-03-29T08:46:09.000Z","dependencies_parsed_at":"2022-09-26T20:10:29.454Z","dependency_job_id":null,"html_url":"https://github.com/theredrad/udpsocket","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/theredrad/udpsocket","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theredrad%2Fudpsocket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theredrad%2Fudpsocket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theredrad%2Fudpsocket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theredrad%2Fudpsocket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theredrad","download_url":"https://codeload.github.com/theredrad/udpsocket/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theredrad%2Fudpsocket/sbom","scorecard":{"id":880043,"data":{"date":"2025-08-11","repo":{"name":"github.com/theredrad/udpsocket","commit":"f00c34730313c1042c3d1575115a414aee36e328"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 3 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2024-2611 / GHSA-8r3f-844c-mc37"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T07:46:49.131Z","repository_id":47615852,"created_at":"2025-08-24T07:46:49.131Z","updated_at":"2025-08-24T07:46:49.131Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28480513,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["encryption","end-to-end-encryption","game-server","go","golang","udp","udp-server","udp-socket"],"created_at":"2026-01-16T18:00:55.977Z","updated_at":"2026-01-16T18:01:27.644Z","avatar_url":"https://github.com/theredrad.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# udpsocket\n\nI made this package to make a virtual stateful connection between the client \u0026 server using the UDP protocol for a golang game server (as you know the UDP protocol is stateless, packets may not arrive in order \u0026 there is no ACK).\n\nThe `udpsocket`  supports a mimic of DTLS handshake, cryptography, session management \u0026 authentication. It's responsible to make a secure channel between the client \u0026 server on UDP (handshake), authenticate \u0026 manage them, decrypt \u0026 encrypt the data \u0026 provide an API to send or broadcast data to the clients \u0026 listen to them.\n\n## Using\n\nThe `udpsocket` server accepts some parameters to initiate:\n\n* An instance of `*net.UDPConn`\n* An array of options\n\n### Options\n\nOptions can be passed using pre-defined functions:\n\n```go\nudpsocket.WithAuthClient()\nudpsocket.WithTranscoder()\nudpsocket.WithSymmetricCrypto()\nudpsocket.WithAsymmetricCrypto()\nudpsocket.WithReadBufferSize()\nudpsocket.WithMinimumPayloadSize()\nudpsocket.WithProtocolVersion()\nudpsocket.WithHeartbeatExpiration()\nudpsocket.WithLogger()\n```\n\n#### AuthClient\n\nAn implementation of `AuthClient` interface that is used to authenticting the user token. If your server doesn't need any token-based authentication, don't pass anything, so the Server will use the default implementation of it which has no authentication.\n\n#### Transcoder\n\nAn implementation of `Transcoder` interface that is used to encode \u0026 decode the data between the client \u0026 server. The default implementation of the `Transcoder` is `Protobuf`. The transcoder can encode \u0026 decode some default message types, like handshake \u0026 ping \u0026 also supports general `Marshal` \u0026 `Unmarshal` methods to support your custom data type.\n\n#### Symmetric encryption\n\nAn implementation of `crypto.Symmetric` interface to encrypt \u0026 decrypt via Symmetric-keys algorithms. The default implementation uses `AES CBC` with `PKCS#7` padding.\n\n#### Asymmetric encryption\n\nAn implementation of `crypto.Asymmetric` interface to encrypt \u0026 decrypt via public-key cryptography. The default implementation uses `RSA` with custom key size.\n\n#### Other configs\n\n* `ReadBufferSize`: Size of reading buffer\n* `MinimumPayloadSize`: to cut the data that has not enough size (to prevent some attack methods)\n* `ProtocolVersionMajor`: the protocol major version\n* `ProtocolVersionMinor`: the protocol minor version\n\n\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"net\"\n    \"fmt\"\n    \"crypto/rsa\"\n    \"time\"\n    \"os\"\n  \n    \"demo/auth\"\n    \"demo/encoding\"\n\t\n    \"github.com/theredrad/udpsocket\"\n    \"github.com/theredrad/udpsocket/crypto\"\n)\n\nvar (\n\tpk *rsa.PrivateKey\n\n\tudpServerIP   = \"127.0.0.1\"\n\tudpServerPort = \"7009\"\n\n\tdefaultRSAPrivateKeySize = 2048\n)\n\nfunc main() {\n    f, err := auth.NewFirebaseClient(context.Background(), \"firebase-config.json\") // firebase implementation of auth client to validate firebase-issued tokens\n    if err != nil {\n        panic(err)\n    }\n    \n    udpAddr, err := net.ResolveUDPAddr(\"udp\", fmt.Sprintf(\"%s:%s\", udpServerIP, udpServerPort))\n    if err != nil {\n        panic(err)\n    }\n    \n    udpConn, err := net.ListenUDP(\"udp\", udpAddr)\n    if err != nil {\n        panic(err)\n    }\n    defer udpConn.Close()\n\n\n    pk, err = crypto.GenerateRSAKey(defaultRSAPrivateKeySize)\n    if err != nil {\n        panic(err)\n    }\n    \n    r := crypto.NewRSAFromPK(pk) // creating a new instance of the RSA implementation\n    if err != nil {\n        panic(err)\n    }\n    \n    a := crypto.NewAES(crypto.AES_CBC) // creating a new instance of the AES implementation\n    \n    t := \u0026encoding.MessagePack{} // an implementation of msgpack for the Transcoder\n    s, err := udpsocket.NewServer(udpConn,\n        udpsocket.WithAuthClient(f),\n        udpsocket.WithTranscoder(t),\n        udpsocket.WithSymmetricCrypto(a),\n        udpsocket.WithAsymmetricCrypto(r),\n        udpsocket.WithReadBufferSize(2048),\n        udpsocket.WithMinimumPayloadSize(4),\n        udpsocket.WithProtocolVersion(1, 0),\n        udpsocket.WithHeartbeatExpiration(3*time.Second),\n        udpsocket.WithLogger(log.New(os.Stdout, \"udp server: \", log.Ldate)),\n    )\n    if err != nil {\n        panic(err)\n    }\n    s.SetHandler(incomingHandler)\n\n    go s.Serve() // start to run the server, listen to incoming records\n    \n    // TODO: need to serve the public key on HTTPS (TLS) to secure the download for the client\n}\n\nfunc incomingHandler(id string, t byte, p []byte) {\n    // handle the incoming\n}\n\n```\n\n\n\n## Send or broadcast a message\n\nThe server exported two methods to send a message to the client or broadcast a message for all clients. To send a message to a certain client, you must have the client ID.\n\n* BroadcastToClients(messageType byte, payload []bytes)\n* SendToClientByID(clientID string, messageType byte, payload []byte)\n\nThe payload is a Transcoder encoded message \u0026 encrypted is handled by the Server.\n\n## Handler\n\nThe handler is a function with `func(id string, t byte, p []byte)` signature. You can set your handler function by the `SetHandler` method. This function is called when a custom type record is received \u0026 authenticated. The `id` parameter is the client ID (which is fetched from the token, or a new generated UUID if no authentication is required), the `t` parameter is the record type \u0026 the `p` parameter is the decrypted payload, you must `Unmarshal` it to your custom message type. \n\n\n\n## Dig deeper\n\n## Record\n\nEach message from the client is a `Record`. The record has a format to parse \u0026 decryption.\n\n```\n 1   0   1   1 0 2 52 91 253 115 22 78 39 28 5 192 47 211...\n|-| |-| |-|  |------------------------------------------|\n a   b   c                        d\n\na: record type\nb: record protocol major version\nc: record protocol minor version\nd: record body\n```\n\nYou may ask why the RSA encrypted message size is prepended to the record while the RSA private key size is known and the encrypted message size is calculatable. The `Server` actually uses `Asymmetric` interface for encryption and the RSA implements it, so maybe in other implementations, the encrypted message size, doesn't follow the private key size, thus we need to pass the encrypted message size in handshaking to be able separate the AES encrypted and RSA encrypted sections.\n\n\n### Record types\n\nThe first byte of record is the type \u0026 indicates how to parse the record. supported reserved types:\n\n* ClientHello : `1`\n* HelloVerify: `2`\n* ServerHello: `3`\n* Ping: `4`\n* Pong: `5`\n\n## Handshake\n\nThe handshake process is made base on a mimic of DTLS protocol, the client sends `ClientHello` record, this record contains a random bytes \u0026 AES key \u0026 encrypted by the server public key, which is needed to download on TLS, then the server generates a random cookie based on the client parameters, encrypts it with the client AES key (which is received by `ClientHello` \u0026 sends it as `HelloVerify` record. The client decrypts the record \u0026 repeats `ClientHello` message with the cookie, the record is needed to be encrypted with the server public key, then append the encrypted user token (with AES) to the record body. the server will registers the client after cookie verification \u0026 authenticates the user token \u0026 then returns a `ServerHello` record contains a random secret session ID. The handshake process is done here.\n\n```\n      Client                                   Server\n      ------                                   ------\n      ClientHello           ------\u003e\n\n                            \u003c-----         HelloVerifyRequest\n                                           (contains cookie)\n\n      ClientHello           ------\u003e\n (with cookie \u0026 token)\n      \n                            \u003c-----           ServerHello\n                                         (contains session ID)\n```\n\n## Encryption\n\nThe `Server` uses both symmetric \u0026 asymmetric encryptions to communicate with the client.\n\n### Handshake\n\nThe `ClientHello` record (for the server) contains a secure 256 bit AES key \u0026 encrypted by the server public key, so the server could decrypt it with the private key.\n\nThe `HelloVerify` record (for the client) encrypts with the client AES key (which was decrypted before).\n\nIf user authentication is required, the client must send the user token with the `ClientHello` (a record which contains cookie too), but the asymmetric encryption has a limitation on size. for example, the RSA only is able to encrypt data to a maximum amount equal to the key size (e.g. 2048 bits = 256 bytes) \u0026 the user token size could be more, so the user token must be encypt by the client AES, then the server could decrypt it after validation of `ClientHello` record.\n\nThe handshake record structure is a little different because of using hybrid encryption. the two bytes after the protocol version bytes indicates the size of the handshake body which is encrypted by the server public key. the handshake body size is passing because of the key size, the encrypted body size depends on the RSA key size.\n\n```\n 1   0   1   1   0   2 52 91 253 115 22 78 39 28 5 192 47 211 ... 4 22 64 91 195 37 225\n|-| |-| |-| |-| |-| |----------------------------------------|   |---------------------|\n a   b   c   d   e                      f                                   g\n\na: record type (1 =\u003e handshake)\nb: record protocol major version (0 =\u003e 0.1v)\nc: record protocol minor version (1 =\u003e 0.1v)\nd: handshake body size ([1] 0 =\u003e 256 bytes, key size: 2048 bits) (first digit number in base of 256)\ne: handshake body size (1 [0] =\u003e 256 bytes, key size: 2048 bits) (second digit number in base of 256)\nf: handshake body which is encrypted by the server public key \u0026 contains the client AES key\ng: user token which is encrypted by the client AES key size\n```\n\n## Other records\n\nAfter a successful handshake, the server returns a secret `SessionID` in the `ServerHello` record which is encrypted by the client AES Key. The client must append the `SessionID` to the custom record bytes, then encrypt it by the AES key, add the record headers (record type \u0026 protocol version), then send the bytes to the server. The server will decrypt the record body by the client AES key (which is registered before with the IP \u0026 port of the client), then parse the `SessionID` from the decrypted body, authorize the session ID \u0026 pass the bytes to the `Handler` function. \n\nFor `Ping` record, the server immidiently sends a `Pong` record to the client.\n\n\n\n## TODO\n\n- [x] Update client lastHeartBeat to close the connection on timeout (delete the client to prevent sending message on the broadcast) \n- [ ] Check the client random bytes for all zero value\n- [x] Add tests\n- [ ] Check the client AES key strength\n- [ ] Add throttling (rate limit, ban IP ...)\n- [ ] Support cipher suites\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheredrad%2Fudpsocket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftheredrad%2Fudpsocket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftheredrad%2Fudpsocket/lists"}