- ymawky macOS के लिए aarch64 assembly में लिखा गया एक छोटा static HTTP server है, जो libc wrapper के बिना केवल Darwin raw system calls का उपयोग करता है
- यह
GET,HEAD,PUT,OPTIONS,DELETE, byte-range requests, directory listing, custom error pages को support करता है, लेकिन nginx का विकल्प बनने के लिए नहीं, बल्कि web server के काम करने के तरीके को समझने के लिए convenience layers हटाकर बनाया गया implementation है - request parsing, percent decoding, header inspection, range value conversion, error handling, file closing, response generation तक सब कुछ सीधे लिखना पड़ता है, और Python में simple string splitting या
int(string)जैसा काम भी assembly में दर्जनों से सैकड़ों लाइनों के validation code में बदल जाता है - server हर नए connection पर
fork()कॉल करने वाली fork-on-request संरचना अपनाता है, इसलिए implementation आसान है, लेकिन concurrent connections की throughput कम हो सकती है और slowloris के प्रति कमजोर हो सकता है; इसी वजह से header timeout औरContent-Lengthआधारित body timeout लागू किए गए हैं PUTपहले.ymawky_tmp_temporary file में लिखता है और सफल होने पर replace करता है, साथ ही path traversal रोकथाम,O_NOFOLLOW_ANY,fstat64(), directory listing में URL encoding और HTML escaping जैसी filesystem safety को भी सीधे handle करता है
ymawky का अवलोकन और सीमाएँ
- ymawky macOS के लिए aarch64 assembly में लिखा गया एक छोटा static HTTP server है
- यह libc wrapper के बिना केवल Darwin raw system calls का उपयोग करता है, और कोई external library या existing parser इस्तेमाल नहीं करता
- supported features हैं
GET,HEAD,PUT,OPTIONS,DELETE, byte-range requests, directory listing, और custom error pages - project की सीमाएँ इस प्रकार हैं
- aarch64 assembly only
- macOS/Darwin target
- raw syscalls only, libc wrappers नहीं
- static files only
- preexisting parsers नहीं
- external libraries नहीं
- इसका उद्देश्य nginx को replace करना नहीं है, बल्कि web server वास्तव में कैसे काम करता है यह समझने के लिए convenience layers हटाकर implementation करना है
assembly में web server बनाते समय क्या-क्या करना पड़ता है
- assembly machine code और high-level languages के बीच की layer है, और
mov,add,ldr,str,cmpजैसे instructions executable binary के bytes से सीधे मेल खाते हैं svc #0x80executable binary केD4 00 10 01bytes का human-readable रूप है- string type न होने के कारण strings memory में लगातार bytes के क्षेत्र के रूप में मौजूद होती हैं, और C के
structजैसी language feature भी नहीं होती, इसलिए field offsets और total size खुद जानने पड़ते हैं - HTTP library, automatic cleanup, exceptions, objects कुछ भी नहीं होने के कारण request parsing, error handling, file closing, response generation जैसे सारे काम सीधे लिखने पड़ते हैं
- गलत काम करने पर भी CPU बिना warning दिए वैसे ही execute करता रहता है, इसलिए समस्या लिखे गए instructions और memory access में ही होती है
raw system calls और server flow
-
Darwin system calls
- ymawky libc wrapper के बजाय kernel को सीधे call करता है
- Darwin aarch64 में system call number
x16register में रखा जाता है, जबकि Linux aarch64 मेंx8में open()system call number5है, और filename व mode जैसे arguments को registers में सीधे रखकरsvc #0x80के माध्यम से kernel को call किया जाता हैopen()fail होने पर carry flag set होता है, औरb.cs open_failedकी तरह carry flag जाँचकर failure-handling code पर branch किया जाता है
-
basic server behavior
- web server का बुनियादी flow request लेना, उसे process करना, और status code व आवश्यक file वापस करना है
- socket setup में
socket(AF_INET, SOCK_STREAM, 0),setsockopt(... SO_REUSEADDR ...),bind(sockfd, &addr, 16),listen(sockfd, 5),accept(sockfd, NULL, NULL)जैसे चरण शामिल हैं - ymawky हर नए connection पर
fork()call करने वाला fork-on-request server है - यह तरीका request handling के बीच memory share नहीं करता, इसलिए इसे समझना और implement करना आसान है, लेकिन process-per-memory-space के कारण overhead बढ़ता है और nginx के event-driven asynchronous non-blocking model की तुलना में concurrent connection throughput कम रहती है
- concurrent connections बढ़ने पर kernel, process के भीतर execution से ज्यादा समय process switching में खर्च कर सकता है
-
request handling में जरूरी काम
- request method
GET,HEAD,OPTIONS,PUT,DELETEमें से कौन-सी है यह पहचानना - request path निकालना और
%20जैसी percent encoding decode करना - path safety checks करना, और client द्वारा भेजे गए header fields parse करना
- requested file की जानकारी लाकर यह तय करना कि वह directory है या regular file
PUTrequest body को temporary file में लिखना और response headers व body बनाना- खुले हुए files बंद करना और server crash न हो इसके लिए errors handle करना
- request method
HTTP parsing खुद implement करना
-
request line और header termination
- HTTP request एक string है जिसे server को interpret करना होता है, उदाहरण इस प्रकार है
GET /index.html HTTP/1.0\r\n Range: bytes=1-5\r\n\r\n - पहली line में
GETrequest, target fileindex.html, और HTTP versionHTTP/1.0होती है \r\nline ending है, और\r\n\r\nheaders का अंत है- अगर
\r\n\r\nप्राप्त नहीं होता, तो400 Bad Requestके साथ रोकना चाहिए
- HTTP request एक string है जिसे server को interpret करना होता है, उदाहरण इस प्रकार है
-
path extraction
- ymawky supported methods और शुरुआती bytes की तुलना करके request type पहचानता है, फिर path निकालता है
- यह headers को byte-by-byte scan करके
/या*ढूँढता है, लेकिनHTTP/1.0के अंदर के/को path न समझ ले, इसके लिए/से ठीक पहले का byte space है या नहीं यह जाँचता है - उदाहरण के लिए
GET HTTP/1.0\r\n\r\nमेंHTTP/1.0के अंदर/है, इसलिए अगर उसके पहले का byte space न हो तो400 Bad Requestलौटाया जाता है - अधिकांश systems में
PATH_MAX4096 bytes होता है, इसलिए ymawky में 4096-byte filename buffer और null terminator के 1 byte के लिएfilename_buffer: .skip 4097रखा गया है - अगर request path buffer से लंबा हो, तो arbitrary memory overwrite करने के बजाय
414 URI Too Longलौटाना चाहिए - Python के
text.split("GET /")[1].split(" ")[0]जैसे काम को assembly में HTTP validity checks सहित लगभग 200 lines लगती हैं
-
percent decoding और header field validation
- path में
%मिलने पर अगले दो bytes0-9,a-f,A-Fके valid hex digits हैं या नहीं, यह जाँचा जाता है, और फिर उन्हें संबंधित byte value में बदला जाता है GETमेंRange:header हो सकता है औरPUTमेंContent-Length:आवश्यक होता है- ये headers request URL की तरह fixed position पर नहीं होते, इसलिए पूरे header को character-by-character traverse करना पड़ता है
- अगर
\rके बाद\nन हो, या पहले\rके बिना\nआ जाए, तो header को invalid मानकर400 Bad Requestलौटाया जाता है - अगर नई header line space से शुरू होती है, तो header field space से शुरू नहीं हो सकती, इसलिए
400 Bad Requestलौटाया जाता है
- path में
-
string comparison और number conversion
Range:याContent-Length:खोजने के लिए दो string pointersx0,x1और maximum lengthx2लेकर character-by-character तुलना करने वालाstreqnfunction लिखा गया हैRange:header में नीचे की तरह start या end में से कोई एक छूट सकता है, लेकिन दोनों में से कम-से-कम एक होना जरूरी हैRange: bytes=10- Range: bytes=-10 Range: bytes=5-10- range values strings होती हैं, इसलिए ASCII digits को integer में बदलने वाला
atoi-style function चाहिए - 64-bit register overflow से बचने के लिए अगर संख्या 19 digits या उससे ज्यादा की हो तो उसे error माना जाता है
- Python के
int(string)जैसा काम भी assembly में digit validation, multiplication, addition, और carry flag आधारित success/failure signaling को सीधे implement करने की मांग करता है
PUT handling और temporary file strategy
PUTएक idempotent method है, यानी वही request कई बार भेजने पर भी अंतिम server state समान रहती हैPUT /file.txtfile.txtको बनाता है या existing file को पूरी तरह overwrite करता है, और1234को दो बार भेजने पर file content12341234नहीं बल्कि1234रहता है- globally open
PUTखतरनाक हो सकता है, और handling के दौरान ये समस्याएँ ध्यान में रखनी होती हैं- request processing के दौरान process crash हो जाना
- client का
Content-Length2KB बताकर केवल 100 bytes भेजना - client का
Content-Length50GB जैसी बहुत बड़ी value भेजना
config.SमेंMAX_BODY_SIZEdefault रूप से 1GB है, औरContent-Lengthइससे ज्यादा होने पर413 Content Too Largeलौटाया जाता है- existing file को सीधे खोलकर लिखने पर failure की स्थिति में आधी-लिखी file रह सकती है, इसलिए ymawky पहले
.ymawky_tmp_format की temporary file में लिखता है getpid()system call number20से pid लिया जाता है, और customitoa()से उसे string में बदला जाता है, साथ ही buffer overflow की जाँच भी होती है- client body को temporary file में पूरा लिखने और सफलता मिलने पर temporary file को in-place नाम से replace किया जाता है, जिससे requested file server पर बनती है
- अगर client अनपेक्षित रूप से connection तोड़ दे, timeout हो जाए, या invalid body भेजे, तो temporary file को
unlink()system call10याunlinkat()system call472से delete कर दिया जाता है - existing file को केवल तभी overwrite किया जाता है जब पूरी request सफलतापूर्वक transfer हो जाए
directory listing और escaping
GET /somedir/request मिलने परconfig.SमेंALLOW_DIR_LISTINGenabled है या नहीं यह जाँचा जाता है- अगर directory listing disabled हो, तो
403 Forbiddenलौटाया जाता है - enabled होने पर
getdirentries64()system call344से requested directory की file info buffer भरी जाती है - buffer में हर file का नाम और filename length शामिल होती है, और ymawky इन्हीं से clickable HTML बनाता है
- हर file के लिए client को भेजा जाने वाला basic form इस प्रकार है
filename href="..."के अंदर filename को URL path segment के रूप में percent-encode करना पड़ता है, और screen पर दिखने वाले text को HTML-escape करना पड़ता है- अगर filename `&.-~>](%26.-~%3E%3Cfoo)
something evilजैसे body area में XSS संभव बनाने वाले नाम, या">something dastardlyजैसेhref="..."area में XSS संभव बनाने वाले नाम भी execute न हों, इसके लिए encoding की जाती है
network security और timeouts
- slowloris एक denial-of-service attack है जो बहुत सारे connections खोलकर requests को पूरा नहीं करता और server resources को बाँधे रखता है
- ymawky fork-on-request architecture होने के कारण slowloris के प्रति कमजोर हो सकता है
- अगर पूरा header
config.SकेHEADER_REQ_TIMEOUT_SECSके भीतर receive नहीं होता, तो408 Request Timeoutभेजकर connection बंद कर दिया जाता है - request body receive करते समय अगर client बहुत देर तक data न भेजे, तो
config.SकेRECV_TIMEOUTके अनुसार वही प्रक्रिया अपनाई जाती है - केवल per-read timeout पर्याप्त नहीं होता
- कोई malicious client
Content-Length: 1073741823भेजे और हर 9 seconds में 1 byte भेजे, तो content length maximum से 1 byte कम होने के कारण स्वीकार हो जाएगी, और 10-second timeout में 300 साल से अधिक इंतजार करना पड़ सकता है
- कोई malicious client
- इसे कम करने के लिए ymawky
Content-Lengthऔर minimum bytes-per-second के आधार पर timeout की गणना करता हैtimeout = grace_period + content_length / min_bps grace_periodहर body के लिए दिया गया minimum समय है, औरmin_bpsवह सबसे धीमी transfer speed है जिसे server अनुमति देता है- default
min_bps16KB/s है, जो उदार है लेकिन अनंत नहीं - यह तरीका denial-of-service attack को पूरी तरह नहीं रोकता, लेकिन कुछ हमलों में resources के बँधे रहने का समय सीमित करता है
filesystem safety
-
file information check का क्रम
GETऔरHEADमें requested path खोलने के बाद file descriptor परfstat64()system call339चलाकर file type और size जैसी जानकारी ली जाती है- अगर पहले path पर
stat64()system call338चलाया जाए और उसके बाद file खोली जाए, तो check और use के बीच file बदल जाने वाली TOCTOU race condition पैदा हो सकती है
-
docroot और path traversal रोकथाम
- हर request path के आगे docroot जोड़ा जाता है
- default docroot
config.SकेDEFAULT_DIRयानीwww/है /etc/shadowrequestwww/etc/shadowबन जाती है, इसलिए जब तकwww/etc/shadowवास्तव में मौजूद न हो, 404 मिलेगा- लेकिन
/../../../../etc/shadowwww/../../../../etc/shadowबनकर docroot के बाहर resolve हो सकती है, इसलिए अतिरिक्त बचाव जरूरी है - ymawky केवल
..string वाले हर path को reject नहीं करता, बल्कि केवल उन path segments को reject करता है जो ठीक-ठीक..हों %2E%2Edecode होने के बाद..बनता है, इसलिए यह जाँच percent decoding के बाद करनी चाहिए
-
symbolic links handling
- POSIX का
O_NOFOLLOWflag यह सुनिश्चित करता है कि अगर अंतिम path component symbolic link हो, तोopen()fail हो जाए - Darwin का
O_NOFOLLOW_ANYयह सुनिश्चित करता है कि path का कोई भी component symbolic link हो, तो call fail हो जाए - अगर कोई docroot के भीतर खास symbolic link डाल सकता है, तो शायद पहले से कोई और बड़ी समस्या मौजूद है, लेकिन यह flag अतिरिक्त सुरक्षा देता है
- POSIX का
Apple-specific behavior
-
timeout handling और
sigaction()- request timeout implement करने के लिए
setitimer()system call83से कुछ समय बादSIGALRMभेजना पड़ता है - default रूप से
SIGALRMchild को मार देता है, लेकिन ymawky पहले408 Request Timeoutभेजना चाहता है - इसके लिए
sigaction()system call46का उपयोग होता है - Darwin का raw
sigactionstructsa_trampfield को expose करता है - सामान्यतः libc
sa_trampset करके stack और registers save करता है,sigreturnतैयार करता है, और फिर handler पर branch करता है - ymawky का timeout handler
408 Request Timeoutभेजता है, जरूरी चीजें बंद करता है, और child को exit कर देता है, इसलिए उसे return करने की जरूरत नहीं होती - इसलिए trampoline slot को सीधे timeout response चलाने वाले code की ओर point कराया जाता है, और
sa_handlerवsigreturnको bypass किया जाता है
- request timeout implement करने के लिए
-
proc_info()और child process count limit- Apple में running processes और उनके child की जानकारी लेने के लिए कम documented
proc_info()system call336उपलब्ध है - यह call आम तौर पर
ps,lsof,topजैसे tools में उपयोग होती है - ymawky active child processes की गिनती के लिए
proc_info()का उपयोग करता है - maximum connections configurable हैं, इसलिए live child count जानना जरूरी है
proc_info()child process information को buffer में लिखता है, और हर element का size ज्ञात होने के कारण written bytes से child count निकाला जा सकता है- अगर child count
MAX_PROCSसे ऊपर हो जाए, तो नए connections को503 Service Unavailableके साथ reject किया जाता है
- Apple में running processes और उनके child की जानकारी लेने के लिए कम documented
निष्कर्ष और project जानकारी
- static web server में कठिन हिस्सा sockets खोलना और listen करना नहीं, बल्कि request parsing और सभी edge cases को संभालना था
- requests, paths, responses सब bytes हैं; range requests को सटीक होना पड़ता है और filenames को उनकी position के हिसाब से अलग-अलग escape करना पड़ता है
- assembly request parsing, memory management, error handling, string conversion, timeouts, और file safety जैसे हर काम को सीधे लिखने पर मजबूर करती है
- ymawky को imtomt maintain करते हैं
1 टिप्पणियां
Lobste.rs की राय
कमाल है। पहले मैंने एक छोटी कंपनी के साथ इंटीग्रेशन का काम किया था जो smart devices बनाती थी, और उस कंपनी का इकलौता engineer assembly language के अलावा कुछ जानता ही नहीं था
hardware control code से लेकर server operating system, और यहाँ तक कि हमारे द्वारा इस्तेमाल किया जाने वाला JSON web API भी सब कुछ उसने सीधे assembly में लिखा था
एक बार हमें ऐसा bug मिला जहाँ web API किसी बिल्कुल अलग device का data लौटाता था, और बाद में पता चला कि operating system scheduling system में off-by-one error थी, जिसकी वजह से “database” web service को गलत row वापस दे रहा था
“आत्महत्या” जैसे expressions को संभालते समय कृपया content warning लगाएँ। इससे भी बेहतर है कि उसका ज़िक्र ही न किया जाए
यह comment देखने के बाद मैंने फिर से ढूँढा, लेकिन तब भी नहीं मिला — क्या मुझसे कुछ छूट गया?
“सब कुछ assembly में लिखा गया” वाली बात देखकर मुझे Therac-25 investigation report याद आ गई