ircu2/FLUXURI_DETALIATE_IRCD.md

24 KiB
Raw Permalink Blame History

🔄 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()

// 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()

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()

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()

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()

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:

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

// 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()

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()

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)

// 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:

// 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
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

// 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

// 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

// 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
  // ...
}

// 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:

// 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:

// 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

// 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ă.