Skip to content

Latest commit

 

History

21 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

goed2k-server

简体中文

github.com/chenjia404/goed2k-server is an ED2K/eMule server implemented in Go, compatible with the github.com/monkeyWie/goed2k client protocol.

The current release focuses on three areas:

  • ED2K/eMule TCP and UDP server protocol
  • HTTP admin API with an embedded Web UI
  • Static shared catalog persistence (JSON / MySQL / PostgreSQL)

The goal is not to replicate the full official eMule server, but to provide a runnable, testable, extensible server foundation so you can extend protocol behavior and business logic.

Features

ED2K TCP protocol (implemented)

  • Client login handshake LoginRequest
  • Server status Status and message Message
  • Client ID assignment IdChange (eMule extended fields: ReportedIP, obfuscation port)
  • Shared file registration OP_OFFERFILES
  • Search SearchRequest (eMule recursive boolean tree and goed2k flat encoding)
  • User search OP_SEARCH_USER (empty user list reply)
  • Search pagination SearchMore
  • File source lookup GetFileSources / OP_GETSOURCES_OBFU
  • Callback request CallbackRequest (TCP 0x1C)
  • Callback notification CallbackRequestIncoming and failure CallbackRequestFailed
  • TCP protocol obfuscation (DH + RC4)

ED2K UDP protocol (implemented)

When server_udp is enabled, the server listens on TCP port + udp_port_offset (default +4) and supports:

Opcode Description
0x96 Global server status OP_GLOBSERVSTATREQ / OP_GLOBSERVSTATRES
0x98 Global file search OP_GLOBSEARCHREQ, replies with 0x99 per result
0x9A File source lookup OP_GLOBGETSOURCES, reply 0x9B
0x9C Callback relay OP_GLOBCALLBACKREQ → TCP 0x35 to target client
0x9E Callback failure OP_INVALID_LOWID when target is offline
0xA2 Server info request, reply 0xA3
0xA4 Server list request, reply 0xA1 (empty list today)

Runtime (implemented)

  • Dynamic user table and runtime statistics
  • Dynamic shared file index (in-memory; search and source lookup)
  • Static catalog persisted to JSON, MySQL, or PostgreSQL
  • HTTP admin API and embedded Web UI (Chinese/English at /)
  • Admin API token authentication, pagination, filtering, sorting
  • Low-ID ReportedIP fill (reported_public_ip or auto-detected public source IP)
  • Health checks

Project layout

Install and import

Run as a CLI

If the module is published on GitHub, install by module path:

go install github.com/chenjia404/goed2k-server/cmd/goed2k-server@latest

Then run:

goed2k-server -config config.json

Import as a Go module

To use the server package in your own project:

go get github.com/chenjia404/goed2k-server@latest

Import:

import "github.com/chenjia404/goed2k-server/ed2ksrv"

goed2k dependency version

This project depends on the remote module:

github.com/monkeyWie/goed2k v0.0.0-20260319015208-6257e6988ff2

Check the resolved version:

go list -m github.com/monkeyWie/goed2k

Upgrade to the latest upstream:

go get github.com/monkeyWie/goed2k@latest
go mod tidy

Pin to a specific pseudo-version:

go get github.com/monkeyWie/goed2k@v0.0.0-20260319015208-6257e6988ff2
go mod tidy

After upgrading, run:

go test ./...

Notes:

  • github.com/monkeyWie/goed2k does not have stable tags yet, so Go uses pseudo-versions
  • A pseudo-version maps to a specific commit and is suitable for reproducible builds
  • When upstream publishes semantic tags, you can switch to those

Requirements

  • Go 1.25+
  • Network access to the github.com/monkeyWie/goed2k module

Quick start

1. Create a config file

Copy the example:

cp config.example.json config.json

Example contents:

{
  "listen_address": ":4661",
  "admin_listen_address": ":8080",
  "admin_token": "change-me",
  "server_name": "goed2k-server",
  "server_description": "Minimal eD2k/eMule compatible server",
  "message": "Welcome to goed2k-server",
  "storage_backend": "json",
  "catalog_path": "testdata/catalog.json",
  "database_dsn": "",
  "database_table": "shared_files",
  "search_batch_size": 2,
  "tcp_flags": 0,
  "aux_port": 0,
  "protocol_obfuscation": true,
  "server_udp": true,
  "udp_port_offset": 4,
  "soft_files_limit": 5000,
  "hard_files_limit": 200000,
  "max_users_advertised": 500000
}

See the Configuration table below and config.example.json for all fields.

2. Start the server

From module source:

go run github.com/chenjia404/goed2k-server/cmd/goed2k-server -config config.json

Or from a local clone:

go run ./cmd/goed2k-server -config config.json

Default listeners:

  • ED2K TCP: :4661
  • HTTP admin: :8080
  • ED2K UDP (optional, see below): TCP listen port + udp_port_offset (default +4, so with TCP 4661 the UDP port is 4665)

UDP port (eMule / aMule clients)

eMule sends a global server status request over UDP (OP_GLOBSERVSTATREQ). After the server replies with OP_GLOBSERVSTATRES, the client can refresh soft file limit, hard file limit, max users, and related server-list fields. TCP-only connections often leave those at 0.

  • Port formula: UDP port = TCP listen port + udp_port_offset. The default udp_port_offset is 4 (same convention as common eD2k clients, matching aMule’s default SendUDPPacket offset).
  • Disable UDP: set "server_udp": false to skip the UDP listener (clients may still show zeros or stale stats).
  • Firewall / security groups: if server_udp is enabled, open the matching UDP port in addition to the ED2K TCP port.

Docker

The image published for production is chenjia404/goed2k-server on Docker Hub.

Pull:

docker pull chenjia404/goed2k-server:latest

The container entrypoint runs /app/goed2k-server with default arguments -config /app/config.json (see the Dockerfile in this repo). Map host ports and mount your config.json at /app/config.json:

docker run -d --name goed2k-server \
  -p 4661:4661 -p 4665:4665/udp -p 8080:8080 \
  -v /path/to/config.json:/app/config.json:ro \
  chenjia404/goed2k-server:latest

4665:4665/udp matches default TCP 4661 with udp_port_offset 4. If you change the TCP port in listen_address, map TCP port + udp_port_offset for UDP.

When storage_backend is json, ensure catalog_path refers to a file that exists inside the container—usually by mounting your catalog and pointing catalog_path at that path. Example: host files under /srv/goed2k/, with catalog_path set to /data/catalog.json:

docker run -d --name goed2k-server \
  -p 4661:4661 -p 4665:4665/udp -p 8080:8080 \
  -v /srv/goed2k/config.json:/app/config.json:ro \
  -v /srv/goed2k/catalog.json:/data/catalog.json:ro \
  chenjia404/goed2k-server:latest

To use another config path, pass arguments after the image name (overriding the default -config /app/config.json):

docker run --rm -p 4661:4661 -p 4665:4665/udp -p 8080:8080 \
  -v /path/to/other.json:/other/config.json:ro \
  chenjia404/goed2k-server:latest -config /other/config.json

To build and run from source instead of the Hub image, use the Dockerfile at the repository root.

Configuration

Field Description
listen_address ED2K TCP listen address
admin_listen_address HTTP admin listen address
admin_token Admin token; if non-empty, requests must send X-Admin-Token
server_name Server name
server_description Server description
message Message clients receive after connecting
storage_backend Persistence backend: json, mysql, or pgsql
catalog_path Path to the static shared catalog file for the json backend
database_dsn Connection string for mysql or pgsql
database_table Table name (default shared_files)
search_batch_size Results per search page
tcp_flags TCP flags returned in IdChange
aux_port Auxiliary port returned in IdChange
protocol_obfuscation Enable eMule-style TCP obfuscation (DH + RC4) when the first byte is not ED2K
server_udp Enable UDP server status replies (default true)
udp_port_offset UDP listen port offset from TCP (default 4, i.e. TCP 4661 → UDP 4665)
soft_files_limit Soft file limit advertised in the UDP reply (eMule display and publish policy)
hard_files_limit Hard file limit advertised in the UDP reply
max_users_advertised Max users advertised in the UDP reply
reported_public_ip Public IPv4 filled into IdChange.ReportedIP for low-ID clients; auto-detected from public source IP when empty

UDP callback

High-ID clients may request a low-ID peer to call back via UDP OP_GLOBCALLBACKREQ (0x9C) without sending TCP 0x1C.

Standard payload (10 bytes): <requester IP 4><requester TCP port 2><target client_ID 4>

Legacy 4-byte payload (target client_ID only): requester must already be logged in over TCP from the same source IP as the UDP packet.

On success the server sends TCP CallbackRequestIncoming (0x35) to the target; on failure UDP 0x9E is returned.

Database storage examples

MySQL:

{
  "storage_backend": "mysql",
  "database_dsn": "user:password@tcp(127.0.0.1:3306)/goed2k?charset=utf8mb4&parseTime=true",
  "database_table": "shared_files"
}

PostgreSQL:

{
  "storage_backend": "pgsql",
  "database_dsn": "postgres://user:password@127.0.0.1:5432/goed2k?sslmode=disable",
  "database_table": "shared_files"
}

When using a database backend:

  • Tables are created automatically on startup
  • The static catalog is loaded from the database into the in-memory index
  • Admin API create/delete/persist operations write back to the database
  • Runtime OP_OFFERFILES dynamic shares remain in memory only

Shared catalog format

The shared catalog is a JSON file pointed to by catalog_path.

Example:

{
  "files": [
    {
      "hash": "31D6CFE0D16AE931B73C59D7E0C089C0",
      "name": "ubuntu-24.04-desktop-amd64.iso",
      "size": 6144000000,
      "file_type": "Iso",
      "extension": "iso",
      "sources": 12,
      "complete_sources": 10,
      "endpoints": [
        {
          "host": "127.0.0.1",
          "port": 4662
        }
      ]
    }
  ]
}

Field reference

Field Description
hash ED2K file hash
name File name
size File size
file_type File type, e.g. Iso, Audio
extension Extension
media_codec Media codec (optional)
media_length Media duration (optional)
media_bitrate Media bitrate (optional)
sources Source count; defaults to endpoints length if omitted
complete_sources Complete sources; defaults to sources if omitted
endpoints List of source addresses returned to clients

Dynamic shared file registration

After login, clients can register shared files with OP_OFFERFILES (0x15).

Current behavior:

  • Reported files enter a runtime dynamic index
  • The dynamic index participates in search and source queries
  • On disconnect, dynamic shares are revoked automatically
  • Dynamic shares are not written to the static catalog.json

This is session/runtime data and is not mixed with the static catalog maintained via the HTTP admin API in the same persistence layer.

HTTP admin API

Authentication

When admin_token is set, include:

X-Admin-Token: change-me

Response shape

Success:

{
  "ok": true,
  "data": {},
  "meta": {}
}

Error:

{
  "ok": false,
  "error": "message"
}

Health

GET /healthz

GET /api/healthz

Example:

curl http://127.0.0.1:8080/healthz

Statistics

GET /api/stats

Example:

curl -H 'X-Admin-Token: change-me' \
  http://127.0.0.1:8080/api/stats

Client list

GET /api/clients

Query parameters:

  • search: filter by client name, remote address, listen endpoint, or client hash (substring)
  • page: page number (default 1)
  • per_page: page size (default 50, max 500)
  • sort: id, name, connected_at, last_seen_at

Example:

curl -H 'X-Admin-Token: change-me' \
  'http://127.0.0.1:8080/api/clients?search=test&page=1&per_page=20&sort=name'

Client detail

GET /api/clients/{id}

Example:

curl -H 'X-Admin-Token: change-me' \
  http://127.0.0.1:8080/api/clients/2130706433

File list

GET /api/files

Query parameters:

  • search: filter by file name or hash (substring)
  • file_type: filter by file type
  • extension: filter by extension
  • page: page number (default 1)
  • per_page: page size (default 50, max 500)
  • sort: name, size, sources

Example:

curl -H 'X-Admin-Token: change-me' \
  'http://127.0.0.1:8080/api/files?search=ubuntu&file_type=Iso&sort=size&page=1&per_page=10'

File detail

GET /api/files/{hash}

Example:

curl -H 'X-Admin-Token: change-me' \
  http://127.0.0.1:8080/api/files/31D6CFE0D16AE931B73C59D7E0C089C0

Create or update file

POST /api/files

Example:

curl -X POST \
  -H 'X-Admin-Token: change-me' \
  -H 'Content-Type: application/json' \
  http://127.0.0.1:8080/api/files \
  -d '{
    "hash":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "name":"runtime-added-demo.mp3",
    "size":4096,
    "file_type":"Audio",
    "extension":"mp3",
    "endpoints":[{"host":"127.0.0.9","port":4662}]
  }'

Delete file

DELETE /api/files/{hash}

Example:

curl -X DELETE \
  -H 'X-Admin-Token: change-me' \
  http://127.0.0.1:8080/api/files/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Persist catalog manually

POST /api/persist

Example:

curl -X POST \
  -H 'X-Admin-Token: change-me' \
  http://127.0.0.1:8080/api/persist

Tests

Run all tests:

go test ./...

Coverage includes:

  • Search request decoding (eMule recursive tree and goed2k flat encoding)
  • ED2K handshake and extended IdChange
  • Shared file registration OP_OFFERFILES
  • Search, pagination, and user search OP_SEARCH_USER
  • Source lookup (including obfuscated replies)
  • TCP and UDP callback relay
  • UDP global search, source lookup, and server status
  • Admin API auth, Web UI, health checks, catalog persistence, statistics

Current limitations

  • The reference goed2k client does not yet send OP_OFFERFILES; end-to-end dynamic sharing needs client support
  • Dynamic share index is in-memory only; admin APIs manage the static catalog only
  • Advanced publish flows (incremental updates, fine-grained publish state) are not implemented
  • Multiple TCP clients behind the same public IP share one high client ID (assigned by source IP); later logins replace earlier sessions
  • Single admin_token only; no RBAC; audit log is in-memory (last 200 entries)
  • No OpenAPI/Swagger documentation
  • Not a full eMule server: many TCP/UDP opcodes are unimplemented

Suggested next steps

  1. Add OP_OFFERFILES send logic in the goed2k client
  2. Add OpenAPI docs and Swagger UI
  3. Persist audit logs and add RBAC
  4. Label dynamic vs static shares in admin APIs
  5. Optional SQLite storage backend

About

go语言版本ed2k-server

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages