Skip to main content

Overview

Soulseek uses a custom binary protocol over persistent TCP connections. There are two distinct connection roles:
  • Server connection — one long-lived TCP connection to the central Soulseek server (server.slsknet.org:2242) used for authentication, search dispatch, peer address lookups, room lists, and the distributed network handshake.
  • Peer connections — direct TCP connections between clients, established on demand for file browsing, transfers, and search result delivery.
SeeleSeek’s primary protocol reference is Nicotine+, the open-source Python client that has the most complete community-maintained documentation of the Soulseek protocol.

Message Framing

Every message — to the server or to a peer — uses the same envelope:
┌─────────────────────────────────────┐
│  Length   (4 bytes, little-endian)  │  Total bytes that follow
│  Code     (4 bytes, little-endian)  │  Message type identifier
│  Payload  (variable)                │  Message-specific fields
└─────────────────────────────────────┘
The length field counts the code field plus the payload — it does not include the 4 bytes of the length field itself. All multi-byte integers are little-endian. Strings are length-prefixed with a UInt32 byte count followed by UTF-8 bytes. In SeeleSeek, MessageBuilder serializes outgoing messages and MessageParser reassembles incoming byte streams into complete frames.
// Example: building a search message
nonisolated static func fileSearchMessage(token: UInt32, query: String) -> Data {
    var payload = Data()
    payload.appendUInt32(ServerMessageCode.fileSearch.rawValue)  // code
    payload.appendUInt32(token)
    payload.appendString(query)                                   // UInt32 length + UTF-8
    return wrapMessage(payload)                                   // prepends UInt32 length
}

Connection Flow

After a TCP connection to the server is established, the client performs this sequence:
1

Login (code 1)

Send username, password, client version (169), an MD5 hash of username + password, and a minor version. The server replies with a success flag, a greeting string, and the client’s external IP address.
2

Set listen port (code 2)

Tell the server which TCP port the client is listening on for incoming peer connections. SeeleSeek reports both a standard port and an obfuscated port.
3

Report shared files (code 35)

Send the count of shared folders and files so the server can display the client’s share stats to other users.
4

Join the distributed network

Send HaveNoParent (code 71) to indicate the client needs a distributed search parent, then BranchLevel (code 126) and BranchRoot (code 127) once a parent is assigned. Send AcceptChildren (code 100) to volunteer as a relay node.
5

Keepalive

Send a Ping (code 32) every 5 minutes to prevent NAT timeout and server-side disconnection.

Server Message Codes

The ServerMessageCode enum in MessageCode.swift defines every recognized server message. Key codes:
CodeNameDirection
1LoginC→S / S→C
2SetListenPortC→S
28SetOnlineStatusC→S
32PingC→S
34SendDownloadSpeedC→S
35SharedFoldersFilesC→S
41ReloggedS→C (another login detected)
92CheckPrivilegesC→S / S→C
CodeNameDescription
3GetPeerAddressRequest IP/port for a username
5WatchUserSubscribe to status updates for a user
7GetUserStatusOne-shot status query
18ConnectToPeerServer relays a connection request token
36GetUserStatsRequest speed and share stats
1001CantConnectToPeerServer could not relay the request
CodeNameDescription
13SayInChatRoomSend a room message
14JoinRoomJoin a public or private room
15LeaveRoomLeave a room
16UserJoinedRoomAnother user joined
17UserLeftRoomAnother user left
64RoomListFull list of rooms and user counts
22PrivateMessagesSend/receive private messages
23AcknowledgePrivateMessageMark message as read
CodeNameDescription
71HaveNoParentClient needs a parent node
93EmbeddedMessageServer forwards a distributed message
100AcceptChildrenClient volunteers as relay
102PossibleParentsServer sends candidate parent nodes
126BranchLevelClient reports its depth in the tree
127BranchRootClient reports its tree root username
130ResetDistributedServer resets the distributed state

Peer Connections

Connection Types

Each peer connection is opened with a declared type:
Type stringConnectionType enumPurpose
"P".peerGeneral peer messages (browse, search replies, user info)
"F".fileRaw file transfer data
"D".distributedDistributed search relay

Peer Message Codes

Peer messages use the same length+code framing. Key codes from PeerMessageCode:
CodeNameDescription
0PierceFirewallIndirect connection response (NAT traversal)
1PeerInitHandshake — declare username, connection type, token
4SharesRequestRequest the peer’s full share list
5SharesReplyZlib-compressed share list response
8SearchRequestDistributed search query (forwarded by parent)
9SearchReplySearch results from the peer
15UserInfoRequestRequest profile (description + avatar)
16UserInfoReplyProfile response
36FolderContentsRequestRequest files in a specific folder
37FolderContentsReplyFolder file listing
40TransferRequestInitiate a file transfer
41TransferReplyAccept or deny a transfer
43QueueUploadQueue a download request (called QueueDownload internally)
44PlaceInQueueReplyReport queue position
46UploadFailedUpload could not complete
50UploadDeniedUpload was refused with a reason string
51PlaceInQueueRequestAsk for current queue position

SharesReply Encoding

The shares reply payload is zlib-compressed. The uncompressed structure is:
UInt32  directory count
  for each directory:
    String  directory name
    UInt32  file count
      for each file:
        UInt8   code byte (always 1)
        String  filename
        UInt64  size in bytes
        String  extension (e.g. "mp3")
        UInt32  attribute count
          for each attribute:
            UInt32  attribute type
            UInt32  attribute value

Direct vs Indirect Connections (NAT Traversal)

Soulseek clients are often behind NAT. The protocol handles this with two connection paths:

Direct connection

Client A sends a ConnectToPeer (code 18) to the server with a random token and the target username. The server relays the request. Client B opens a TCP connection directly to A’s listen port and sends PeerInit with the token to identify itself.

Indirect connection (PierceFirewall)

If a direct connection fails, client A tries to open a connection to B’s listen port instead, and sends PierceFirewall (code 0) with the token. B recognizes the token and treats this incoming connection as the expected peer link. This is the fallback for clients where inbound connections are blocked.
In SeeleSeek, PeerConnectionPool.onPierceFirewall handles incoming indirect connections and routes them to the correct pending request — either a browse operation or a download managed by DownloadManager.

Distributed Search Network

The distributed network is a tree overlay that propagates search queries without flooding the central server.
Server
  └── Branch root (level 0)
        └── Child node (level 1)
              └── SeeleSeek client (level 2)
  • After login, send HaveNoParent to request a parent assignment.
  • The server replies with PossibleParents (code 102) — a list of candidate usernames.
  • Connect to one as a D-type peer connection and exchange BranchLevel / BranchRoot distributed messages.
  • Report the resulting level and root back to the server with codes 126 and 127.
  • Send AcceptChildren = true (code 100) to become a relay node.
When a search query arrives over a D connection, SeeleSeek checks its own shares and sends a SearchReply directly to the querying peer, then forwards the DistributedSearchRequest to any connected children. Distributed message codes (carried inside EmbeddedMessage or directly over D connections):
CodeName
0DistributedPing
3DistributedSearchRequest
4BranchLevel
5BranchRoot
7ChildDepth
93EmbeddedMessage

SeeleSeek Protocol Extensions

SeeleSeek defines private peer message codes in the 10000+ range, under SeeleSeekPeerCode. These are silently ignored by other clients.
CodeNamePayload
10000HandshakeUInt8 version — sent after PeerInit to identify SeeleSeek peers
10001ArtworkRequestUInt32 token + String filePath — request embedded album art
10002ArtworkReplyUInt32 token + raw image bytes (may be empty if none found)
When a peer sends a Handshake, PeerConnection sets isSeeleSeekPeer = true, enabling artwork requests between SeeleSeek clients.

References

  • Nicotine+ Protocol Documentation — community-maintained reverse-engineered specification
  • Soulseek Network — official client and network home page
  • seeleseek/Core/Network/Protocol/MessageCode.swift — canonical enum of all codes used in SeeleSeek
  • seeleseek/Core/Network/Protocol/MessageBuilder.swift — serialization for every outgoing message type