938 lines
24 KiB
Markdown
938 lines
24 KiB
Markdown
# 🔄 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ă.*
|
||
|