ircu2/FLUXURI_DETALIATE_IRCD.md

938 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🔄 FLUXURI DETALIATE - Underchat IRCD
**Data**: 23 Februarie 2026
**Complement la**: ANALIZA_ARHITECTURA_SENIOR.md
---
## 📊 DIAGRAMĂ GENERALĂ - DATA FLOW
```
┌─────────────┐
│ CLIENT │
│ (IRC App) │
└──────┬──────┘
│ TCP/SSL
│ Port 6667/6697
┌─────────────────────────────────────────┐
│ LISTENER (s_bsd.c) │
│ ┌───────────────────────────────┐ │
│ │ accept() → new socket │ │
│ │ set non-blocking │ │
│ │ add to LocalClientArray[] │ │
│ │ register with epoll │ │
│ └───────────────────────────────┘ │
└─────────────────┬───────────────────────┘
┌─────────────────────────────────────────┐
│ EVENT LOOP (ircd_events.c) │
│ ┌───────────────────────────────┐ │
│ │ while(running) { │ │
│ │ epoll_wait() → events │ │
│ │ for each event: │ │
│ │ if EPOLLIN → READ │ │
│ │ if EPOLLOUT → WRITE │ │
│ │ if EPOLLERR → ERROR │ │
│ │ } │ │
│ └───────────────────────────────┘ │
└─────────┬───────────────┬───────────────┘
│ │
READ PATH WRITE PATH
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ READ HANDLER │ │ WRITE HANDLER │
│ (s_bsd.c) │ │ (send.c) │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
[Flow A] [Flow B]
```
---
## 🔵 FLOW A: MESSAGE RECEIVING (Client → Server)
### Step 1: Socket Read
**Fișier**: `ircd/s_bsd.c` - funcția `read_packet()`
```c
// Pseudocod simplificat
int read_packet(struct Client* cptr, int ready_to_read) {
char readbuf[SERVER_TCP_WINDOW]; // 32KB buffer
// 1. Read from socket
length = recv(cli_fd(cptr), readbuf, sizeof(readbuf), 0);
if (length <= 0) {
// Connection closed or error
return exit_client(cptr);
}
// 2. Append to client's receive buffer (DBuf)
if (dbuf_put(&cli_recvQ(cptr), readbuf, length) < 0) {
// Out of memory
return exit_client_msg(cptr, "Buffer allocation error");
}
// 3. Process complete messages
while (has_complete_line(&cli_recvQ(cptr))) {
dolen = dbuf_get(&cli_recvQ(cptr), readbuf, sizeof(readbuf));
if (IsServer(cptr))
server_dopacket(cptr, readbuf, dolen);
else
connect_dopacket(cptr, readbuf, dolen);
}
return 1;
}
```
**Caracteristici**:
- ✅ Non-blocking read
- ✅ Buffering pentru mesaje incomplete
- ✅ Procesare iterativă (multiple mesaje per read)
- ⚠️ RISC: Fără limită pe dimensiunea DBuf
---
### Step 2: Packet Processing
**Fișier**: `ircd/packet.c` - funcția `connect_dopacket()`
```c
int connect_dopacket(struct Client *cptr, const char *buffer, int length) {
const char* src = buffer;
char* endp = cli_buffer(cptr) + cli_count(cptr);
// Procesează byte cu byte
while (length-- > 0) {
*endp = *src++;
// Detectează end-of-line
if (IsEol(*endp)) { // CR sau LF
if (endp == cli_buffer(cptr))
continue; // Skip LF/CR gol
*endp = '\0'; // Null-terminate
// ★ PARSE MESSAGE
if (parse_client(cptr, cli_buffer(cptr), endp) == CPTR_KILLED)
return CPTR_KILLED;
// Reset buffer
endp = cli_buffer(cptr);
}
else if (endp < cli_buffer(cptr) + BUFSIZE) {
++endp;
}
}
cli_count(cptr) = endp - cli_buffer(cptr);
return 1;
}
```
**FLUX**:
```
Buffer: "PRIVMSG #test :Hello\r\nNICK newname\r"
Iterație 1: Găsește \r după "Hello"
→ parse_client("PRIVMSG #test :Hello")
Iterație 2: Găsește \r după "newname"
→ parse_client("NICK newname")
```
---
### Step 3: Message Parsing
**Fișier**: `ircd/parse.c` - funcția `parse_client()`
```c
int parse_client(struct Client *cptr, char *buffer, char *bufend) {
struct Message* mptr;
char* para[MAXPARA + 2]; // parametrii comenzii
int paramcount;
// 1. Extrage prefix (dacă există)
// Format: :prefix COMMAND param1 param2 :trailing
if (*buffer == ':') {
// Skip prefix pentru client messages
while (*buffer != ' ' && *buffer)
buffer++;
}
// 2. Extrage comanda
char* command = buffer;
while (*buffer != ' ' && *buffer)
buffer++;
*buffer++ = '\0';
// 3. Lookup în trie
mptr = find_command(command); // O(k) lookup
if (!mptr) {
// ERR_UNKNOWNCOMMAND
return send_reply(cptr, ERR_UNKNOWNCOMMAND, command);
}
// 4. Extrage parametrii
paramcount = parse_params(buffer, para, MAXPARA);
// 5. Verificări
if (mptr->parameters < paramcount) {
return send_reply(cptr, ERR_NEEDMOREPARAMS, mptr->cmd);
}
// 6. ★ CALL HANDLER
MessageHandler handler = mptr->handlers[cli_status(cptr)];
return (*handler)(cptr, cptr, paramcount, para);
}
```
**Trie Lookup Example**:
```
Command: "PRIVMSG"
msg_tree:
'P' → 'R' → 'I' → 'V' → 'M' → 'S' → 'G' → [Message*]
{MSG_PRIVATE, handlers[]}
```
---
### Step 4: Handler Execution
**Fișier**: `ircd/m_privmsg.c` - funcția `m_privmsg()`
```c
int m_privmsg(struct Client* cptr, struct Client* sptr,
int parc, char* parv[]) {
// parv[0] = source nick (implicit)
// parv[1] = target (nick or #channel)
// parv[2] = message text
// 1. Validare parametrii
if (parc < 2 || EmptyString(parv[1])) {
return send_reply(sptr, ERR_NORECIPIENT, "PRIVMSG");
}
if (parc < 3 || EmptyString(parv[2])) {
return send_reply(sptr, ERR_NOTEXTTOSEND);
}
char* target = parv[1];
char* text = parv[2];
// 2. Determine target type
if (IsChannelName(target)) {
// ★ CHANNEL MESSAGE
struct Channel* chptr = FindChannel(target);
if (!chptr)
return send_reply(sptr, ERR_NOSUCHCHANNEL, target);
if (!can_send_to_channel(sptr, chptr))
return send_reply(sptr, ERR_CANNOTSENDTOCHAN, target);
// ★ BROADCAST la toți membrii canalului
sendcmdto_channel_butone(sptr, CMD_PRIVMSG, chptr,
cli_from(sptr), "%s :%s", target, text);
}
else {
// ★ PRIVATE MESSAGE
struct Client* acptr = FindUser(target);
if (!acptr)
return send_reply(sptr, ERR_NOSUCHNICK, target);
// ★ SEND direct
sendcmdto_one(sptr, CMD_PRIVMSG, acptr, "%s :%s", target, text);
}
return 0;
}
```
---
## 🟢 FLOW B: MESSAGE SENDING (Server → Client)
### Step 1: Message Queue Addition
**Fișier**: `ircd/send.c` - funcția `sendcmdto_one()`
```c
void sendcmdto_one(struct Client *from, const char *cmd,
struct Client *to, const char *pattern, ...) {
va_list vl;
struct MsgBuf *mb;
// 1. Format message
va_start(vl, pattern);
mb = msgq_vmake(to, pattern, vl);
va_end(vl);
// 2. Prepend command and source
// Format final: ":source COMMAND params\r\n"
msgq_append(to, mb, ":%s %s", cli_name(from), cmd);
// 3. Add to target's send queue
msgq_add(&cli_sendQ(to), mb, 0); // 0 = normal priority
// 4. Mark connection as having data to send
if (MsgQLength(&cli_sendQ(to)) > 0)
update_write(to); // Register for EPOLLOUT
msgq_clean(mb); // Cleanup message buffer
}
```
**Priority Queue**:
```c
struct MsgQ {
struct MsgQList prio; // Priority messages (server-to-server)
struct MsgQList queue; // Normal messages (client)
};
// Server messages → prio queue
// Client messages → normal queue
// Send order: prio first, then queue
```
---
### Step 2: Write Readiness
**Fișier**: `ircd/ircd_events.c` + `ircd/engine_epoll.c`
```c
// Event loop detectează EPOLLOUT
void engine_loop(struct Engine* eng) {
struct epoll_event events[MAXEVENTS];
int nfds = epoll_wait(epoll_fd, events, MAXEVENTS, timeout);
for (int i = 0; i < nfds; i++) {
struct Socket* sock = events[i].data.ptr;
struct Client* cptr = sock->s_data;
if (events[i].events & EPOLLOUT) {
// ★ Socket is writable
event_generate(ET_WRITE, sock, 0);
// Call registered callback
(*sock->s_callback)(sock->s_events);
}
}
}
```
---
### Step 3: Message Flushing
**Fișier**: `ircd/send.c` - funcția `send_queued()`
```c
void send_queued(struct Client *to) {
if (IsBlocked(to) || !can_send(to))
return;
// Process send queue
while (MsgQLength(&cli_sendQ(to)) > 0) {
unsigned int len;
// ★ DELIVER message
len = deliver_it(to, &cli_sendQ(to));
if (len > 0) {
// Successfully sent 'len' bytes
msgq_delete(&cli_sendQ(to), len);
cli_lastsq(to) = MsgQLength(&cli_sendQ(to)) / 1024;
if (IsBlocked(to)) {
// Socket buffer full, stop for now
update_write(to); // Re-register for EPOLLOUT
return;
}
}
else {
// Error or blocking
if (IsDead(to))
exit_client(to, to, &me, cli_info(to));
return;
}
}
// Send queue empty, no more EPOLLOUT needed
update_write(to);
}
```
---
### Step 4: Actual Socket Write
**Fișier**: `ircd/s_bsd.c` - funcția `deliver_it()`
```c
unsigned int deliver_it(struct Client *cptr, struct MsgQ *mq) {
struct iovec iov[IOV_MAX]; // Scatter-gather I/O
unsigned int len;
int count, bytes;
// 1. Map MsgQ to iovec array
count = msgq_mapiov(mq, iov, IOV_MAX, &len);
// 2. Write with writev (zero-copy)
bytes = writev(cli_fd(cptr), iov, count);
if (bytes < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
// Socket buffer full
SetBlocked(cptr);
return 0;
}
// Real error
dead_link(cptr, "Write error");
return 0;
}
cli_sendB(cptr) += bytes; // Update statistics
return bytes;
}
```
**writev() Advantages**:
```
Instead of:
send(fd, msg1, len1);
send(fd, msg2, len2);
send(fd, msg3, len3);
Use:
iov[0] = {msg1, len1};
iov[1] = {msg2, len2};
iov[2] = {msg3, len3};
writev(fd, iov, 3); // ★ Single syscall
```
---
## 🔴 FLOW C: CHANNEL BROADCAST
**Scenario**: User A sends message to #test (100 members)
```c
// m_privmsg.c
void sendcmdto_channel_butone(struct Client *from, const char *cmd,
struct Channel *chptr,
struct Client *one,
const char *pattern, ...) {
struct Membership *member;
struct MsgBuf *mb;
// 1. Format message once
mb = msgq_make(NULL, pattern, ...);
// 2. Iterate all channel members
for (member = chptr->members; member; member = member->next_member) {
struct Client *dest = member->user;
// Skip sender and specified 'one'
if (dest == from || dest == one)
continue;
// ★ Add to each member's sendQ
msgq_add(&cli_sendQ(dest), mb, 0);
// Mark as needing write
if (MsgQLength(&cli_sendQ(dest)) > 0)
update_write(dest);
}
msgq_clean(mb);
}
```
**Performance Impact**:
```
O(N) where N = channel members
For #test with 100 members:
- 1 message format
- 100 msgq_add() calls
- 100 update_write() calls
- Next event loop: 100× send_queued()
Bottleneck: CPU-bound (single thread)
```
---
## ⚡ FLOW D: SERVER-TO-SERVER (P10 Protocol)
### Message Format
**P10 Numeric Encoding**:
```
Client format: :Nick!user@host PRIVMSG #test :Hello
P10 format: ABCDE P #test :Hello
Where:
AB = Server numeric (2 chars)
CDE = User numeric (3 chars)
P = PRIVMSG token
```
**Encoding Table**:
```c
// numnicks.c
static const char convert2y[] = {
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','[',']'
};
// Base64-like: 64^3 = 262,144 possible users per server
```
### Server Link Flow
```
Local Server (AA) Remote Server (BB)
│ │
│ 1. CONNECT initiated │
│ ──────────────────────> │
│ │
│ 2. PASS :password │
│ <────────────────────── │
│ │
│ 3. SERVER name │
│ <────────────────────── │
│ │
│ 4. BURST mode │
│ <══════════════════════ │
│ - All users │
│ - All channels │
│ - All modes │
│ ══════════════════════> │
│ │
│ 5. END_OF_BURST │
│ <────────────────────── │
│ │
│ 6. Normal operation │
│ <═══════════════════════> │
```
**BURST Example**:
```
// Server BB sends its state to AA
N AliceUser 1 1234567890 alice alice.host +i B]AAAB ABAAA :Alice
│ │ │ │ │ │ │ │ └─ realname
│ │ │ │ │ │ │ └─ account
│ │ │ │ │ │ └─ user numeric
│ │ │ │ │ └─ modes
│ │ │ │ └─ hostname
│ │ │ └─ username
│ │ └─ timestamp
│ └─ hopcount
└─ NICK command (token: N)
B #test 1234567890 ABAAA:o,ABAAB
│ │ └─ members (ABAAA is op, ABAAB normal)
│ └─ timestamp
└─ BURST command
```
---
## 🛡️ FLOW E: FLOOD PROTECTION
### IPcheck Rate Limiting
```c
// IPcheck.c - ip_registry_check_connect()
int ip_registry_check_connect(const struct irc_in_addr *addr,
time_t *next_target_out) {
struct IPRegistryEntry *entry = ip_registry_find(addr);
if (!entry) {
// First connection from this IP
entry = ip_registry_new_entry();
ip_registry_canonicalize(&entry->addr, addr);
entry->connected = 1;
entry->attempts = 1;
entry->last_connect = NOW;
ip_registry_add(entry);
return 1; // ✅ ALLOW
}
// Check clone limit
if (entry->connected >= IPCHECK_CLONE_LIMIT) {
return 0; // ❌ REJECT: Too many clones
}
// Check connection rate
time_t elapsed = CONNECTED_SINCE(entry->last_connect);
if (elapsed < IPCHECK_CLONE_DELAY) {
// Too fast
if (entry->attempts++ > IPCHECK_CLONE_PERIOD) {
return 0; // ❌ REJECT: Connection flood
}
}
else {
// Reset attempt counter
entry->attempts = 1;
}
entry->connected++;
entry->last_connect = NOW;
return 1; // ✅ ALLOW
}
```
**Decay Algorithm**:
```
Time: 0s 10s 20s 30s 40s
Connect: ✓ ✓ ✓ [decay] ✓
Attempts: 1 2 3 1 2
If attempts > CLONE_PERIOD within CLONE_DELAY → REJECT
```
---
### Target Rate Limiting
```c
// IPcheck.c - ip_registry_check_target()
int ip_registry_check_target(struct Client *sptr, void *target,
const char *name) {
struct IPRegistryEntry *entry =
ip_registry_find(&cli_ip(sptr));
if (!entry->target)
entry->target = allocate_targets();
// Update free targets (token bucket)
unsigned int free_targets =
entry->target->count +
(CONNECTED_SINCE(entry->last_connect) / TARGET_DELAY);
if (free_targets > STARTTARGETS)
free_targets = STARTTARGETS;
// Check if already talking to this target
for (int i = 0; i < MAXTARGETS; i++) {
if (entry->target->targets[i] == target_hash(target))
return 0; // ✅ Known target, no cost
}
// New target
if (free_targets == 0) {
// ❌ REJECT: No free targets
send_reply(sptr, ERR_TARGETTOOFAST, name,
TARGET_DELAY * (STARTTARGETS - free_targets));
return 1;
}
// Consume one target
free_targets--;
add_target(entry, target);
return 0; // ✅ ALLOW
}
```
**Example**:
```
User starts with 10 targets (STARTTARGETS)
Sends PRIVMSG to:
#channel1 → 9 targets left
#channel2 → 8 targets left
...
#channel10 → 0 targets left
#channel11 → ❌ ERR_TARGETTOOFAST
After TARGET_DELAY seconds:
Targets regenerate: 0 → 1 → 2 → ... → 10 (max)
```
---
## 🐛 FLOW F: ERROR HANDLING
### Socket Error Detection
```c
// s_bsd.c - read_packet()
int read_packet(struct Client* cptr, int ready) {
int length = recv(cli_fd(cptr), readbuf, sizeof(readbuf), 0);
if (length < 0) {
// Error handling
int err = errno;
switch (err) {
case EWOULDBLOCK:
case EAGAIN:
// Not actually ready (spurious wakeup)
return 1; // OK, try later
case ECONNRESET:
// Connection reset by peer
return exit_client_msg(cptr, cptr, &me,
"Connection reset by peer");
case ETIMEDOUT:
// Connection timed out
return exit_client_msg(cptr, cptr, &me,
"Connection timed out");
default:
// Other error
return exit_client_msg(cptr, cptr, &me,
"Read error: %s", strerror(err));
}
}
if (length == 0) {
// EOF (connection closed gracefully)
return exit_client_msg(cptr, cptr, &me,
"Connection closed");
}
// Normal processing
// ...
}
```
---
### Dead Link Marking
```c
// send.c - dead_link()
static void dead_link(struct Client *to, char *notice) {
// ★ Don't exit immediately, mark for cleanup
SetFlag(to, FLAG_DEADSOCKET);
// Clear buffers to prevent further operations
DBufClear(&cli_recvQ(to));
MsgQClear(&cli_sendQ(to));
client_drop_sendq(cli_connect(to));
// Save error message
ircd_strncpy(cli_info(to), notice, REALLEN + 1);
// Notify opers
if (!IsUser(to) && !IsUnknown(to) && !HasFlag(to, FLAG_CLOSING))
sendto_opmask_butone(0, SNO_OLDSNO, "%s for %s",
cli_info(to), cli_name(to));
}
```
**Main Loop Cleanup**:
```c
// ircd.c - event_loop()
while (running) {
event_loop_iteration();
// Check for dead sockets
for (i = 0; i <= HighestFd; i++) {
cptr = LocalClientArray[i];
if (cptr && IsDead(cptr)) {
exit_client(cptr, cptr, &me, cli_info(cptr));
}
}
}
```
---
## 📊 TIMING DIAGRAMS
### Normal PRIVMSG Latency
```
Time Event
───── ──────────────────────────────────────
0ms Client A: send("PRIVMSG #test :Hi\r\n")
0.1ms Server: recv() → DBuf → parse
0.2ms Server: Handler m_privmsg()
│ ├─ Validate
│ ├─ Find channel
│ └─ Broadcast
0.3ms Server: 100× msgq_add() (channel members)
0.4ms Server: update_write() × 100
[Next event loop iteration]
10ms Server: epoll_wait() → EPOLLOUT events
11ms Server: send_queued() → writev() × 100
12ms Clients: recv() message
TOTAL: ~12ms latency (local network)
```
---
### SendQ Overflow Scenario
```
Time Event
───── ──────────────────────────────────────
0s Client A connected, sendQ = 0 KB
1s Server → Client: 100 messages
│ sendQ = 50 KB
│ Client not reading (slow network)
2s Server → Client: 100 more messages
│ sendQ = 100 KB
│ ⚠️ WARNING: High sendQ
3s Server → Client: 100 more messages
│ sendQ = 150 KB
│ ⚠️ CRITICAL: Very high sendQ
4s kill_highest_sendq() triggered
│ ❌ Client A disconnected
│ Reason: "SendQ exceeded"
✅ Memory freed, server stable
```
**Prevention**:
```c
// Recommended: Per-client hard limit
#define MAX_SENDQ_USER 65536 // 64KB
if (MsgQLength(&cli_sendQ(to)) > MAX_SENDQ_USER) {
dead_link(to, "SendQ exceeded");
return;
}
```
---
## 🔍 DEBUGGING FLOWS
### Debug Logging Example
```c
// Enable with /SET DEBUG level
Debug((DEBUG_INFO, "Client %s (%s) connected from %s",
cli_name(cptr), cli_username(cptr), cli_sock_ip(cptr)));
Debug((DEBUG_SEND, "Sending to %s: %s",
cli_name(to), message));
Debug((DEBUG_ERROR, "Failed to send to %s: %s",
cli_name(to), strerror(errno)));
```
**Output** (ircd.log):
```
[12:34:56] [INFO] Client Alice (alice) connected from 192.168.1.100
[12:34:57] [SEND] Sending to Alice: :server.test 001 Alice :Welcome
[12:34:58] [ERROR] Failed to send to Bob: Connection reset by peer
```
---
## 🎯 CRITICAL PATHS (Performance Hotspots)
### 1. Message Parsing (Most Frequent)
**File**: `parse.c`
**Frequency**: Every message received
**Optimization**:
- ✅ Trie lookup O(k)
- ✅ Token-based P10 (shorter strings)
- ❌ Still does byte-by-byte parsing
### 2. Channel Broadcast (Most Expensive)
**File**: `send.c`, `channel.c`
**Frequency**: Every channel message
**Complexity**: O(N) where N = members
**Optimization**:
- ✅ writev() reduces syscalls
- ✅ Message formatted once
- ❌ No message coalescing for multiple broadcasts
### 3. Event Loop (Always Running)
**File**: `ircd_events.c`, `engine_epoll.c`
**Frequency**: Continuous
**Optimization**:
- ✅ epoll() scales to 10K+ connections
- ✅ Edge-triggered mode reduces wakeups
- ❌ Single-threaded CPU bottleneck
---
## 📚 REFERINȚE SUPLIMENTARE
### Command Flow Examples
**USER Registration**:
```
Client → Server: NICK Alice
Server → Client: (no response, waiting for USER)
Client → Server: USER alice 0 * :Alice Smith
Server → Client: :server 001 Alice :Welcome to IRC
:server 002 Alice :Your host is server
:server 003 Alice :This server was created...
:server 004 Alice server version modes
:server 005 Alice FEATURES :are supported
(MOTD)
:server 376 Alice :End of MOTD
```
**Channel JOIN**:
```
Client → Server: JOIN #test
Server processing:
1. Validate channel name
2. Check if channel exists
3. Check bans/invite-only
4. Add user to channel
5. Broadcast JOIN to all members
6. Send channel topic
7. Send NAMES list
Server → Client: :Alice!alice@host JOIN #test
Server → Client: :server 332 Alice #test :Channel topic
Server → Client: :server 333 Alice #test topic_setter 1234567890
Server → Client: :server 353 Alice = #test :@Alice Bob Carol
Server → Client: :server 366 Alice #test :End of NAMES
```
---
*Document generat de Senior Software Architect - Complement la analiza principală.*