{"id":22945217,"url":"https://github.com/curiousci/networking","last_synced_at":"2025-04-01T21:44:04.638Z","repository":{"id":229152456,"uuid":"775898476","full_name":"CuriousCI/networking","owner":"CuriousCI","description":"Tiny DNS server which gives the same answer everytime!","archived":false,"fork":false,"pushed_at":"2024-04-02T13:53:38.000Z","size":88,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-07T14:24:54.699Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CuriousCI.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-22T09:11:28.000Z","updated_at":"2024-03-27T15:43:41.000Z","dependencies_parsed_at":"2024-03-22T10:32:15.504Z","dependency_job_id":"51fd13e7-4fa3-4e06-92ef-e06fc19e0575","html_url":"https://github.com/CuriousCI/networking","commit_stats":null,"previous_names":["curiousci/dns","curiousci/networking"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CuriousCI%2Fnetworking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CuriousCI%2Fnetworking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CuriousCI%2Fnetworking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CuriousCI%2Fnetworking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CuriousCI","download_url":"https://codeload.github.com/CuriousCI/networking/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246716889,"owners_count":20822535,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-14T14:30:05.800Z","updated_at":"2025-04-01T21:44:04.622Z","avatar_url":"https://github.com/CuriousCI.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DNS _(perché SMTP non mi piaceva!)_\n\nPer curiosità ho voluto implementare uno dei protocolli applicativi che abbiamo visto a lezione. **HTTP** lo avevo già implementato nel primo semestre _(lo scrissi in [Rust](https://www.rust-lang.org/), insieme ad un collega che lo [fece in C](https://github.com/Warcophyr/C-web-server))_\n\nHo provato ad implementare SMTP, ma è una tortura a cui non voglio più sottomettermi 😅, e dopo aver visto DNS a lezione ho provato con quello _(spoiler: riuscendoci 😁!)_\n\nIl codice è un po' incasinato _(lo scritto in un tempo relativamente breve)_, ma dovrebbe dare comunque l'idea _(e ho imparato un po' di cose sul protocollo)_\n\n_(il server l'ho scritto in [Rust](https://www.rust-lang.org/) btw)_\n\n## Il server\n\nAprire una porta e leggere i pacchetti UDP è facilissimo! Si tratta di fare il `bind()` con un **socket** (coppia **ip** e **porta**) se il sistema operativo lo permette, e creare un `loop` infinito che legge i dati dalla porta e li mette in un `buffer` _(un array)_.\n\nLa prima scoperta interessante è che se si usa UDP, il pacchetto DNS ha come limite **512 byte**, quindi non bisogna gestire un buffer dinamico, se ne può creare uno di dimensione fissata!\n\nQuando ricevo un pacchetto UPD, mi da la dimensione del pacchetto ricevuto e l'indirizzo da cui l'ho ricevuto _(con `nslookup` la porta cambia per ogni richiesta, in genere un numero alto come 50000)_\n\n```Rust\nlet socket = UdpSocket::bind(SocketAddr::from(([127, 0, 0, 1], 53))).unwrap();\n\nlet mut buffer = [0; 512]; // Ringrazio il limite di 512 byte, mi facilita il lavoro 😁!\nloop {\n    let (size, source) = socket.recv_from(\u0026mut buffer).unwrap();\n\n    // codice ...\n\n    let request = DNSMessage::from(Box::from(buffer));\n    let response = DNSMessage { ... } // Lo vediamo dopo 😉\n\n    // codice ...\n\n    socket.send_to(\u0026response, source).unwrap(); // Non è proprio così, ma l'idea è quella\n}\n```\n\nNella foto un esempio di richiesta con `nslookup` _(ne fa più di 1)_ e dei dati che riceve tramite UDP il server _(dopo ne faccio un'analisi)_\n\n![](./nslookup-requests.png)\n\n## La verità dietro ai pacchetti DNS!\n\nUn messaggio DNS completo ha tutti i campi indicati sotto... sono pochi, vero? Oltre ai campi visti a lezione, ci sono 8 byte dopo i **flag** che servono ad indicare il numero di richieste, risposte _(autoritative, non autoritative e aggiuntive)_, ed è con questo meccanismo che si fa il _parsing_ di un messaggio DNS senza separatori: sapendo in anticipo quanti _oggetti_ bisogna leggere\n\n\u003e `Box\u003c[Tipo]\u003e` sta semplicemente ad indicare un array allocato sull'**heap**, con variabili del tipo `Tipo`, nulla di troppo strano\n\n```Rust\nstruct DNSMessage {\n    identification: u16,\n    query_reply: QueryReply, // 1 bit\n    opcode: OpCode, // 4 bit\n    authoritative_answer: bool, // 1 bit\n    truncation: bool, // 1 bit, perché DNS su UDP ha un limite di 512 byte\n    recursion_desired: bool, // 1 bit\n    recursion_available: bool, // 1 bit\n    _zero: (), // 3 bit, riservato per usi futuri\n    response_code: ResponseCode, // 4 bit\n    number_of_questions: u16, // 2 byte\n    number_of_answers: u16, // 2 byte\n    number_of_authority_rrs: u16, // 2 byte\n    number_of_additional_rrs: u16, // 2 byte\n    questions: Box\u003c[Question]\u003e,\n    answers: Box\u003c[ResourceRecord]\u003e,\n    authority_rrs: Box\u003c[ResourceRecord]\u003e,\n    additional_rrs: Box\u003c[ResourceRecord]\u003e,\n}\n\nenum QueryReply {\n    Query,\n    Reply,\n}\n\nenum OpCode {\n    Query,\n    InverseQuery,\n    Status,\n}\n\n// C'è spazio anche per usi futuri\n#[non_exhaustive]\nenum ResponseCode {\n    NoError,\n    FormatError,\n    ServerFail,\n    NonexistentDomain,\n}\n\n// Non ho messo tutti i tipi, dato che sono tantissimi, e c'è spazio per usi futuri!\n#[non_exhaustive]\nenum DNSRecordType {\n    A,\n    NS,\n    CNAME,\n    MX,\n    TXT,\n    AAAA,\n}\n\nstruct Question {\n    name: Box\u003c[u8]\u003e, // variabile, e vediamo dopo come viene gestito!\n    dns_type: DNSRecordType, // 2 byte\n    class_code: u16, // 2 byte\n}\n\nstruct ResourceRecord {\n    name: Box\u003c[u8]\u003e, // variabile\n    dns_type: DNSRecordType, // 2 byte\n    class_code: u16, // 2 byte\n    ttl: u32, // 4 byte\n    rd_length: u16, // 2 byte\n    r_data: Box\u003c[u8]\u003e, // variabile\n}\n```\n\nMa come facciamo a gestire i campi variabili, ad esempio il dominio?\n\nConsideriamo `google.com.wind3.hub`, viene rappresentato così:\n\n[6, 103, 111, 111, 103, 108, 101, 3, 99, 111, 109, 5, 119, 105, 110, 100, 51, 3, 104, 117, 98, 0]\n\n- il primo byte, con valore **6**, indica che i prossimi 6 caratteri fanno parte del dominio\n- infatti `[103, 111, 111, 103, 108, 101]` è proprio la rappresentazione in ASCII di `google`\n- proseguendo, abbiamo il valore **3**, stando ad indicare che i prossimi 3 caratteri fanno parte del dominio\n- infatti `[99, 111, 109]` è la rappresentazione di `com`\n- si procede così finché non si arriva ad un byte con valore `0` (l'ultimo byte dell'array) che sta ad indicare la fine del dominio\n\n\u003e Quindi nella richiesta, il dominio non viene separato dal carattere `.`, è ricavato implicitamente dalla struttura del messaggio\n\nIl codice per parsare il dominio è relativamente semplice\n\n```Rust\nlet mut index = 12;\nlet mut length = bytes[index];\nindex += 1;\nlet mut domain = vec![];\nlet mut questions = vec![];\n\nfor _ in 0..number_of_questions {\n    while length \u003e 0 {\n        for _ in 0..length {\n            domain.push(bytes[index]);\n            index += 1;\n        }\n        domain.push(b'.');\n        length = bytes[index];\n        index += 1;\n    }\n    questions.push(domain); // è un po' più complicato di così, ma non di troppo\n    domain.clear();\n    index += 4;\n}\n```\n\n## Struttura di una risposta\n\n```Rust\nlet response = DNSMessage {\n    identification: request.identification,\n    query_reply: QueryReply::Reply,\n    opcode: OpCode::Query,\n    authoritative_answer: false,\n    truncation: false,\n    recursion_desired: false,\n    recursion_available: false,\n    _zero: (),\n    response_code: ResponseCode::NoError,\n    number_of_questions: 0,\n    number_of_answers: 1,\n    number_of_authority_rrs: 0,\n    number_of_additional_rrs: 0,\n    questions: Box::new([]),\n    answers: Box::new([ResourceRecord {\n        name: request.questions[0].name.clone(),\n        dns_type: DNSRecordType::A,\n        class_code: request.questions[0].class_code,\n        ttl: 10, // 10 secondi\n        rd_length: 4,\n        r_data: Box::new([0, 0, 10, 8, 123, 5]),\n    }]),\n    authority_rrs: Box::new([]),\n    additional_rrs: Box::new([]),\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcuriousci%2Fnetworking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcuriousci%2Fnetworking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcuriousci%2Fnetworking/lists"}