22 KiB
📊 ANALIZĂ ARHITECTURALĂ SENIOR - Underchat IRCD (C)
Data Analiză: 23 Februarie 2026
Versiune Proiect: v1.7.5
Analist: Senior Software Architect (Protocoale de rețea & Sisteme distribuite)
Limbaj: C (confirmat - nu Python)
Bază: UnderNet IRCU2 - Protocol P10 (RFC 1459/2812)
🎯 EXECUTIVE SUMMARY
Underchat IRCD este o implementare matură în C a unui server IRC bazat pe codul UnderNet IRCU2. Arhitectura este SINGLE-THREADED cu EVENT LOOP non-blocking, folosind multiplexare I/O avansată (epoll/kqueue/poll/select).
Verdict Rapid:
- ✅ Arhitectură solidă: Event-driven, non-blocking I/O
- ⚠️ Performanță: Single-threaded (limitată de CPU-ul unui singur core)
- ⚠️ Securitate: Câteva utilizări nesigure de strcpy/strcat/sprintf
- ✅ RFC Compliance: Bună conformitate cu RFC 1459/2812
- ⚠️ Memory Management: Potențiale leaks în gestionarea bufferelor
🏗️ ARHITECTURA SISTEMULUI
1. MODEL DE CONCURRENCY
Tip: Single-threaded, Event-driven Architecture
Pattern: Reactor Pattern (event loop centralizat)
┌─────────────────────────────────────────────┐
│ MAIN EVENT LOOP (ircd.c) │
│ │
│ while (running) { │
│ engine_loop() -> epoll_wait()/poll() │
│ ├─ Socket Events (READ/WRITE/ACCEPT) │
│ ├─ Timer Events │
│ └─ Signal Events │
│ } │
└─────────────────────────────────────────────┘
│
├─> Socket Layer (s_bsd.c)
│ ├─ LocalClientArray[MAXCONNECTIONS]
│ ├─ HighestFd tracking
│ └─ Non-blocking sockets
│
├─> Event Engine (ircd_events.c)
│ ├─ Engine abstraction layer
│ ├─ Multiple backends:
│ │ • epoll (Linux) ★ PREFERRED
│ │ • kqueue (BSD/macOS)
│ │ • /dev/poll (Solaris)
│ │ • poll() fallback
│ │ • select() fallback
│ └─ Generator/Event system
│
├─> Parser (parse.c)
│ ├─ Trie-based command lookup
│ ├─ Message dispatch table (msgtab[])
│ └─ Handler functions (m_*.c)
│
└─> Buffer Management
├─ DBuf (input buffers - dbuf.c)
├─ MsgQ (output queues - msgq.c)
└─ Packet processing (packet.c)
1.1 Event Engine Architecture
Fișiere cheie:
ircd/ircd_events.c- Event loop coreircd/engine_epoll.c- Linux epoll backend (PREFERRED)ircd/engine_kqueue.c- BSD kqueue backendircd/engine_poll.c- Poll fallbackircd/engine_select.c- Select fallback
Prioritizare Engine:
static const struct Engine *evEngines[] = {
ENGINE_KQUEUE // BSD/macOS
ENGINE_EPOLL // Linux ★
ENGINE_DEVPOLL // Solaris
ENGINE_FALLBACK // poll() sau select()
0
};
Event Types (enum EventType):
ET_READ- Socket ready for readingET_WRITE- Socket ready for writingET_ACCEPT- New connection availableET_CONNECT- Outbound connection completedET_EOF- Connection closedET_ERROR- Error conditionET_SIGNAL- Signal receivedET_EXPIRE- Timer expiredET_DESTROY- Resource cleanup
2. GESTIONAREA SOCKET-URILOR
2.1 Structura Conexiunilor
LocalClientArray (s_bsd.c:83):
struct Client* LocalClientArray[MAXCONNECTIONS];
int HighestFd = -1;
- Array indexat după file descriptor
- Dimensiune fixă:
MAXCONNECTIONS(tipic 1024-4096) - O(1) lookup pentru evenimente pe socket
HighestFdoptimizează iterarea prin conexiuni active
2.2 Socket States (enum SocketState)
SS_CONNECTING // Conexiune outbound în progres
SS_LISTENING // Socket ascultător
SS_CONNECTED // Socket conectat bidirectional
SS_DATAGRAM // Socket UDP (pentru UPING)
SS_CONNECTDG // UDP conectat
SS_NOTSOCK // Non-socket (pipe pentru semnale)
2.3 Read/Write Flow
READ PATH (s_bsd.c - read_packet()):
recv() → DBuf (cli_recvQ) → packet.c → parse.c → handler (m_*.c)
WRITE PATH (send.c - send_queued()):
Handler → MsgQ (cli_sendQ) → deliver_it() → send()/writev()
Non-blocking cu edge-triggered events (epoll cu EPOLLET).
3. BUFFER MANAGEMENT
3.1 Input Buffers (DBuf)
Fișiere: ircd/dbuf.c, include/dbuf.h
struct DBuf {
unsigned int length; // Bytes în buffer
struct DBufBuffer *head; // Primul chunk
struct DBufBuffer *tail; // Ultimul chunk
};
Caracteristici:
- Linked list de chunked buffers
- Evită realocări mari
- Pool de buffere reutilizabile
DBufAllocCount/DBufUsedCount- tracking memorie
PROBLEMA POTENȚIALĂ:
- ⚠️ Fără limită hard pe lungimea totală a unui DBuf
- Posibil vector de atac DoS prin flood de date incomplete
3.2 Output Queues (MsgQ)
Fișiere: ircd/msgq.c, include/msgq.h
struct MsgQ {
unsigned int length; // Bytes în queue
unsigned int count; // Număr de mesaje
struct MsgQList queue; // Queue normal
struct MsgQList prio; // Queue prioritar
};
Caracteristici:
- Două queue-uri (normal + prioritar)
- Mesajele server-to-server au prioritate
msgq_add()- adaugă cu prioritizaremsgq_mapiov()- mapare pentru writev()
MĂSURI FLOOD PROTECTION:
// send.c:128 - Kill highest sendq când suntem fără memorie
void kill_highest_sendq(int servers_too)
4. PARSING & MESSAGE DISPATCH
4.1 Command Lookup - Trie Data Structure
Fișier: ircd/parse.c
Structură:
struct MessageTree {
struct Message *msg;
struct MessageTree *pointers[MAXPTRLEN]; // 32 pointers
};
static struct MessageTree msg_tree; // Comenzi text (PRIVMSG)
static struct MessageTree tok_tree; // Token-uri P10 (P)
Avantaje:
- O(k) lookup unde k = lungime comandă
- Suport dual: text + P10 tokens
- Rapid pentru comenzile frecvente
Exemplu (parse.c:110):
struct Message msgtab[] = {
{ MSG_PRIVATE, TOK_PRIVATE, 0, MAXPARA, MFLG_SLOW, 0, NULL,
{ m_unregistered, m_privmsg, ms_privmsg, mo_privmsg, m_ignore },
"<target> :<message>"
},
// ... 100+ comenzi
};
4.2 Handler Dispatch
5 handler-uri per comandă:
{ m_unregistered, // UNREG - client neînregistrat
m_privmsg, // CLIENT - user normal
ms_privmsg, // SERVER - inter-server
mo_privmsg, // OPER - operator
m_ignore } // SERVICE - servicii (ignorat)
5. PROTOCOL COMPLIANCE (RFC 1459/2812)
5.1 Conformitate RFC
✅ BINE IMPLEMENTAT:
- Message framing (512 bytes max cu CR-LF)
- Command parsing (prefix, command, params)
- Numeric replies (001-999)
- Channel modes (+o, +v, +b, +k, +l, etc.)
- User modes (+i, +w, +o, etc.)
- Server-to-server protocol (P10)
⚠️ DEVIERI DE LA RFC:
- Protocol P10 (UnderNet specific) - nu este RFC standard
- Numeric nicks (server NN, user NNNNN)
- Extended modes (nu sunt în RFC 1459)
5.2 Message Format
Parsing (packet.c:75-90):
// CORECT: Acceptă CR sau LF ca terminator
if (IsEol(*endp)) {
if (endp == client_buffer)
continue; // Skip extra LF/CR's
*endp = '\0';
if (parse_server(cptr, cli_buffer(cptr), endp) == CPTR_KILLED)
return CPTR_KILLED;
// ...
}
PROBLEMA:
- ⚠️ Acceptă CR SAU LF (nu doar CR-LF)
- Mai permisiv decât RFC, dar backward compatible
🔒 ANALIZĂ SECURITATE
1. INPUT VALIDATION
1.1 Buffer Overflows - RISC MODERAT
PROBLEME IDENTIFICATE:
strcpy/strcat/sprintf Usage (20 instanțe):
// ircd/s_user.c:744 - UNSAFE
strcpy(cli_name(new_client), nick);
// ircd/uping.c:290 - UNSAFE
sprintf(buf, " %10lu%c%6lu", ...);
// ircd/m_whois.c:148-149 - UNSAFE
strcat(markbufp, ", ");
strcat(markbufp, dp->value.cp);
RECOMANDĂRI:
// Înlocuiește cu:
ircd_strncpy(cli_name(new_client), nick, NICKLEN);
ircd_snprintf(buf, sizeof(buf), " %10lu%c%6lu", ...);
✅ BUN: Proiectul folosește deja ircd_strncpy() și ircd_snprintf() în multe locuri.
1.2 UTF-8 Validation
✅ BINE IMPLEMENTAT (ircd_string.c:60):
int string_is_valid_utf8(const char * str)
- Validare completă UTF-8
- Detectează encoding invalid
- Protecție contra caractere de control
2. FLOOD PROTECTION
2.1 Connection Rate Limiting
Fișier: ircd/IPcheck.c
Mecanisme:
#define IPCHECK_CLONE_LIMIT // Max clone de pe același IP
#define IPCHECK_CLONE_PERIOD // Fereastra de timp
#define IPCHECK_CLONE_DELAY // Delay între conexiuni
struct IPRegistryEntry {
unsigned short connected; // Clienți conectați
unsigned char attempts; // Încercări recente
int last_connect; // Timestamp ultima conexiune
};
Hash Table: IP_REGISTRY_TABLE_SIZE = 0x10000 (64K buckets)
IPv6 Handling:
- Doar primii 64 biți sunt considerați (rețea)
- Ultimii 64 biți (host) sunt ignorați
- Protecție contra abuzului de IPv6 /64
✅ BUNĂ PRACTICĂ: Canonicalizare IP pentru comparație.
2.2 Message Rate Limiting
Target Limiting (IPcheck.c:48):
struct IPTargetEntry {
unsigned int count; // Target-uri libere
unsigned char targets[MAXTARGETS]; // Target-uri recente
};
#define MAXTARGETS 20 // Max target-uri
#define STARTTARGETS 10 // Target-uri inițiale
Token Bucket Algorithm:
- Utilizatorii încep cu
STARTTARGETSmesaje - Reîncărcare progresivă:
TARGET_DELAYsecunde - Previne spam cross-channel
2.3 SendQ Limiting
High SendQ Killing (send.c:115):
void kill_highest_sendq(int servers_too) {
// Găsește clientul cu cel mai mare sendQ
// Îl deconectează pentru a elibera memorie
}
⚠️ PROBLEMA:
- Trigger doar când sistemul e deja low memory
- Nu există limită preventivă per-client
- Posibil DoS prin umplerea sendQ
RECOMANDARE:
#define MAX_SENDQ_PER_CLIENT (64 * 1024) // 64KB
if (MsgQLength(&cli_sendQ(client)) > MAX_SENDQ_PER_CLIENT) {
dead_link(client, "SendQ exceeded");
}
3. MEMORY LEAKS - RISC MODERAT
3.1 DBuf/MsgQ Cleanup
✅ BUNĂ PRACTICĂ:
// s_bsd.c - Cleanup la disconnect
DBufClear(&(cli_recvQ(to)));
MsgQClear(&(cli_sendQ(to)));
⚠️ PROBLEME POTENȚIALE:
-
Incomplete Message Buffers:
- Dacă un client trimite date incomplete și nu mai trimite CR-LF
- Buffer rămâne alocat indefinit
FLAG_NONLeste setat dar nu există timeout
-
Event Generator Leaks:
gen_ref_dec()trebuie apelat corect- Dacă reference count nu ajunge la 0 → leak
RECOMANDARE:
// Adaugă timeout pentru conexiuni incomplete
#define INCOMPLETE_MESSAGE_TIMEOUT 30 // secunde
if (HasFlag(cptr, FLAG_NONL) &&
CurrentTime - cli_lasttime(cptr) > INCOMPLETE_MESSAGE_TIMEOUT) {
dead_link(cptr, "Incomplete message timeout");
}
4. RACE CONDITIONS
✅ EVITATE - Single-threaded architecture elimină majoritatea race conditions.
⚠️ EXCEPȚIE: Signal Handlers
// ircd_signal.c - Signal handling
// Handlers scriu într-un pipe non-blocking
// Main loop citește din pipe → safe
BUNĂ PRACTICĂ: Self-pipe trick pentru semnale.
⚡ ANALIZĂ PERFORMANȚĂ
1. SCALABILITATE
1.1 Connection Capacity
LIMITE:
// MAXCONNECTIONS definit la compile-time
// Tipic: 1024-4096
LocalClientArray[MAXCONNECTIONS];
C10K Problem:
- ✅ REZOLVAT prin epoll/kqueue
- ⚠️ Single-threaded → limitat de 1 CPU core
Benchmark Estimate (pe hardware modern):
- ~5,000-10,000 clienți idle
- ~1,000-2,000 clienți activi
- Limitat de CPU pentru parsing/routing
1.2 Message Throughput
Bottleneck-uri:
- Trie Lookup: O(k) - foarte rapid
- Handler Execution: Variabil per comandă
- Broadcast (PRIVMSG la channel mare):
// O(N) unde N = număr membrii for (member in channel->members) sendto_member(member, message);
OPTIMIZARE SUGERATĂ:
- Batch writes cu
writev()✅ (deja implementat) - SendQ prioritization ✅ (deja implementat)
- ❌ LIPSĂ: Message coalescing pentru broadcasts
2. MEMORY FOOTPRINT
2.1 Per-Client Memory
struct Client {
struct Connection* cli_connect; // ~200 bytes
struct User* cli_user; // ~400 bytes (dacă user)
struct Server* cli_serv; // ~300 bytes (dacă server)
// + buffers:
struct DBuf cli_recvQ; // ~8 bytes + date
struct MsgQ cli_sendQ; // ~16 bytes + date
char cli_buffer[BUFSIZE]; // 512 bytes
};
Total per client: ~1-2 KB (fără buffers de date)
Cu buffers: +4-64 KB (variabil, depinde de traffic)
2.2 Memory Pools
✅ BUNĂ PRACTICĂ:
// IPcheck.c:192
static struct IPRegistryEntry* freeList;
// Refolosire structuri în loc de free/malloc repetat
3. LATENCY
Message Latency Path:
Client A → recv() → parse → handler → sendto → Client B's sendQ → send()
↑_____ ~0.1-1ms (local) _____↑
Factori latență:
- Syscall overhead: recv/send
- Event loop iteration: ~1-10ms între iterații
- SendQ flushing: Doar când socket e writable
✅ OPTIMIZARE: Write imediat dacă sendQ e gol
// send.c - try immediate write before queueing
🐛 VULNERABILITĂȚI IDENTIFICATE
CRITICAL (🔴)
Niciuna identificată în analiza curentă
HIGH (🟠)
-
SendQ Exhaustion DoS
- Locație:
send.c - Risc: Client poate umple sendQ până la OOM
- Fix: Limită hard per-client + disconnect proactiv
- Locație:
-
Incomplete Message Buffer Leak
- Locație:
packet.c,s_bsd.c - Risc: Mesaje fără CR-LF rămân în memorie
- Fix: Timeout pentru FLAG_NONL
- Locație:
MEDIUM (🟡)
-
Unsafe String Operations
- Locație: Multiple (20 instanțe strcpy/strcat/sprintf)
- Risc: Buffer overflow dacă input nevalidat
- Fix: Replace with ircd_strncpy/ircd_snprintf
-
IPv6 Clone Detection Limited
- Locație:
IPcheck.c - Risc: Doar /64 verificat, nu full /128
- Fix: Opțiune configurabilă pentru prefix length
- Locație:
LOW (🟢)
- Message Parser Permissiveness
- Locație:
packet.c - Risc: Acceptă CR sau LF (nu doar CR-LF)
- Fix: Strict RFC mode optional
- Locație:
📊 METRICI DE CALITATE COD
Complexitate
| Metric | Valoare | Assessment |
|---|---|---|
| Files | ~150 .c files | Mare |
| Lines of Code | ~50,000+ | Mare |
| Functions | ~1,000+ | Modular |
| Max Function Size | ~500 lines | 🟡 Unele funcții mari |
| Cyclomatic Complexity | Variabilă | 🟡 Averaging medium |
Maintainability
✅ PRO:
- Modularizare clară (per-funcționalitate)
- Headers separate cu API-uri clare
- Comentarii rezonabile
- Coding style consistent
⚠️ CONTRA:
- Global state extensiv (GlobalClientList, etc.)
- Macro-uri complexe (client accessors)
- Unele funcții foarte lungi
- Documentație inline limitată
🎯 RECOMANDĂRI PRIORITIZATE
URGENT (Implementare în 1-2 săptămâni)
-
🔒 Fix Unsafe String Operations
// Replace toate instanțele: strcpy() → ircd_strncpy() strcat() → ircd_strlcat() sau snprintf() sprintf() → ircd_snprintf() -
🛡️ SendQ Hard Limits
#define MAX_SENDQ_USER 65536 // 64KB #define MAX_SENDQ_SERVER 524288 // 512KB // În send.c, verifică înainte de msgq_add() -
⏱️ Incomplete Message Timeout
// În s_bsd.c read_packet(): if (HasFlag(cptr, FLAG_NONL) && timeout_exceeded(cptr)) { dead_link(cptr, "Incomplete message timeout"); }
SHORT-TERM (1-3 luni)
-
📊 Monitoring & Metrics
// Adaugă contoare pentru: - Mesaje procesate/sec - Conexiuni acceptate/respinse - SendQ usage histogram - CPU usage per handler -
🧪 Unit Testing
- Parser tests (trie lookup)
- Buffer management tests (DBuf, MsgQ)
- IPcheck tests (rate limiting)
- Mock socket tests
-
📝 Memory Leak Detection
# Run sub Valgrind valgrind --leak-check=full --track-origins=yes ./ircd
MID-TERM (3-6 luni)
-
⚡ Performance Optimizations
- Message coalescing pentru broadcast
- Zero-copy buffer passing (splice/sendfile)
- JIT compilation pentru hot commands (optional)
-
🔐 Security Hardening
- ASLR verification
- Stack canaries
- Seccomp filters (Linux)
- Pledge/unveil (OpenBSD)
-
📚 Documentation
- Architecture diagrams (flow charts)
- API documentation (Doxygen)
- Security audit report
- Performance tuning guide
LONG-TERM (6-12 luni)
-
🧵 Multi-threading (Optional)
- Thread pool pentru parsing
- Lock-free queues pentru cross-thread messaging
- MASSIVE REFACTOR - evaluat risc/beneficiu
-
🌐 IPv6 Full Support
- Full /128 tracking în IPcheck
- IPv6-only mode
- Dual-stack optimization
-
🔄 Protocol Extensions
- IRCv3 capabilities
- WebSocket support
- Message tags
📈 COMPARAȚIE CU ALTERNATIVE
| Caracteristică | Underchat IRCD | Inspircd | UnrealIRCd | Charybdis |
|---|---|---|---|---|
| Limbaj | C | C++ | C | C |
| Threading | Single | Multi-thread | Multi-thread | Single |
| Event Loop | epoll/kqueue | epoll/kqueue | epoll/kqueue | epoll/kqueue |
| Protocol | P10 (UnderNet) | Modular | Modular | TS6 |
| RFC Compliance | 🟡 Medium | ✅ High | ✅ High | ✅ High |
| Extensibility | 🟡 Medium | ✅ High (modules) | ✅ High (modules) | ✅ High (modules) |
| Memory Safety | 🟡 C (manual) | 🟡 C++ (manual) | 🟡 C (manual) | 🟡 C (manual) |
| Performance | 🟢 Good | ✅ Excellent | ✅ Excellent | 🟢 Good |
| Maturitate | ✅ Mature | ✅ Mature | ✅ Mature | ✅ Mature |
Verdict:
- Underchat IRCD este competitiv pentru rețele mici-medii (< 5000 useri)
- Pentru scale mai mare: consideră InspIRCd (multi-thread) sau Charybdis (TS6)
🎓 CONCLUZIE
Strengths (💪)
- Arhitectură solidă: Event-driven, non-blocking I/O
- Cod matur: Bazat pe UnderNet IRCU2 (20+ ani de dezvoltare)
- Performanță bună: Pentru cazuri de utilizare single-core
- Modularitate: Comenzi separate în m_*.c files
- Portabilitate: Suport multi-platform (Linux, BSD, macOS)
Weaknesses (⚠️)
- Single-threaded: Nu scalează pe multi-core
- Unsafe C: 20+ instanțe de strcpy/strcat/sprintf
- Memory management: Potențiale leaks în edge cases
- Limited flood protection: SendQ fără limită hard
- Protocol lock-in: P10 specific (nu compatibil cu alte rețele)
Risk Assessment
| Categorie | Nivel Risc | Justificare |
|---|---|---|
| Security | 🟡 MEDIUM | Unsafe string ops, dar mitigat de validări |
| Availability | 🟢 LOW | Flood protection rezonabilă |
| Performance | 🟡 MEDIUM | Single-core limited, dar suficient pentru < 5K users |
| Maintainability | 🟢 LOW | Cod matur, modular, bine structurat |
| Scalability | 🟠 HIGH | Hard limit la 1 CPU core, MAXCONNECTIONS fix |
Final Recommendation
DEPLOY cu următoarele condiții:
- ✅ Implementează fix-urile URGENT (unsafe strings, sendQ limits)
- ✅ Monitorizare activă (CPU, memory, connections)
- ✅ Testing extins (load testing, fuzz testing)
- ⚠️ Plan de scale: Pentru >5000 users, consideră alternative multi-thread
PENTRU PRODUCȚIE:
- Perfect pentru comunități mici-medii (< 2000 users)
- Necesită hardening de securitate înainte de expunere publică
- Recomand firewall rules + rate limiting la nivel de rețea
- Monitoring 24/7 obligatoriu
📞 NEXT STEPS
- Discuție tehnică: Prioritizare fix-uri vs. features
- Security audit: Penetration testing extern
- Load testing: Simulare 1000+ clienți concurenți
- Code review: Peer review pentru commits critice
- Documentation sprint: README tehnic + architecture guide
Semnat:
Senior Software Architect
Specialized in Network Protocols & Distributed Systems
Data: 23 Februarie 2026
📚 ANEXE
A. GLOSAR TEHNIC
- P10: Protocol server-to-server folosit de UnderNet
- Numeric nick: Format NNNNN pentru identificare unică
- Trie: Prefix tree pentru lookup rapid comenzi
- Event loop: Main loop care multiplexează I/O
- epoll: Linux kernel API pentru event notification
- SendQ: Queue de mesaje în așteptare pentru trimitere
- DBuf: Dynamic buffer pentru date primite
B. FIȘIERE CHEIE ANALIZATE
ircd/ircd.c # Entry point, main loop
ircd/s_bsd.c # Socket handling, I/O
ircd/ircd_events.c # Event engine core
ircd/engine_epoll.c # Epoll backend
ircd/parse.c # Command parsing (trie)
ircd/packet.c # Packet processing
ircd/send.c # Message sending
ircd/IPcheck.c # Rate limiting
ircd/dbuf.c # Input buffers
ircd/msgq.c # Output queues
include/client.h # Client structure
include/struct.h # User/Server structures
C. REFERINȚE
- RFC 1459: Internet Relay Chat Protocol
- RFC 2812: Internet Relay Chat: Client Protocol
- UnderNet IRCU2: https://github.com/UndernetIRC/ircu2
- Epoll man page: man 7 epoll
- Kqueue man page: man 2 kqueue
Acest document este confidențial și destinat echipei tehnice Underchat.