Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pindjouf committed Dec 14, 2024
0 parents commit d97ec03
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# Virtual environment
env/
venv/

# Distribution / packaging
*.egg-info/
dist/
build/

# IDE settings
.idea/
.vscode/
*.swp
14 changes: 14 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004

Copyright (C) 2004 Sam Hocevar <[email protected]>

Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. You just DO WHAT THE FUCK YOU WANT TO.

99 changes: 99 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Deem and Nets

**Deem and Nets** is a primitive DNS implementation. The name was chosen at random, you could see it in multiple ways:
- Say deem and nets fast and it sounds like DNS
- See it as "Demand nets" since DNS places several demands on networks

You can choose it means whatever.

## Features

- Parses the DNS message header (including flags and counts).
- Server provides structured output to help visualize DNS query components.
- Serves as a foundation for future expansions, such as resolving DNS queries and supporting various DNS record types.

## Directory Structure

```
deem-and-nets
├── README.md # Project documentation
├── requirements.txt # Python dependencies
└── src/ # Source code
├── message/
│   ├── builder.py # Future DNS message builder
│   ├── parser.py # Current DNS message parser
├── records/
│   └── types.py # DNS record types (e.g., A, CNAME, etc.)
├── resolver/
│   ├── cache.py # Future caching mechanism
│   └── resolver.py # Future DNS resolver logic
└── server.py # UDP DNS server
```

## Getting Started

### Prerequisites

- Python 3.12+ (as indicated by the virtual environment)
- `pip` for installing dependencies

### Installation

1. Clone the repository:

```bash
git clone https://github.com/pindjouf/deem-and-nets.git
cd deem-and-nets
```

2. Set up a virtual environment:

```bash
python3 -m venv env
source env/bin/activate
pip install -r requirements.txt
```

### Usage

1. Start the UDP server:

```bash
python src/server.py
```

2. Send a DNS query to the server:

```bash
dig @127.0.0.1 -p 53053 example.com
```

or

```bash
nslookup -port=53053 -querytype=A example.com 127.0.0.1
```

I recommend `nslookup` since it provides valid flags, unlike `dig`.

3. Analyze the parsed DNS header output.

### Current Limitations

- Only parses the DNS message header.
- No support for response generation or DNS record resolution yet.

## Roadmap

- Add DNS response generation.
- Implement caching for better performance.
- Expand support for DNS record types (A, AAAA, CNAME, etc.).
- Implement advanced features like recursion and authoritative responses.

## Contributing

Contributions are welcome! Feel free to open issues or submit pull requests.

## License

This project is licensed under the WTFPL License - see the [LICENSE](LICENSE) file for details.
Empty file added docs/protocol.md
Empty file.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pydantic
2 changes: 2 additions & 0 deletions src/message/builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
query1 = "30940120000100000000000106676f6f676c6503636f6d000001000100002904d000000000000c000a0008ac27df40c4bdd64d"
query2 = "39140120000100000000000106676f6f676c6503636f6d000001000100002904d000000000000c000a00081852df91ed3d89be"
145 changes: 145 additions & 0 deletions src/message/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from pydantic import BaseModel
from typing import List

class DomainLabel(BaseModel):
length: int
value: str

class Flags(BaseModel):
qr: int
opcode: int
authoritative_answer: int
truncation: int
recursion_desired: int
recursion_available: int
zero: int
response_code: int

class Header(BaseModel):
id: bytes
flags: Flags
question_count: int
answer_count: int
authority_count: int
additional_records_count: int

class Question(BaseModel):
labels: List[DomainLabel]
zero_byte_terminator: int
q_type: int
q_class: int

class Query(BaseModel):
header: Header
question: Question

class OpcodeToken:
def get_token(self, flags: str) -> dict:
bin_opcode = "0b" + flags
opcode_value = int(bin_opcode, 2)

opcode_token = ""
match opcode_value:
case 0:
opcode_token = "Standard query"
case 1:
opcode_token = "Inverse query"
case 2:
opcode_token = "Server status request"
case 3:
opcode_token = "Reserved"

return { 'value': opcode_value, 'meaning': opcode_token }

class rCodeToken:
def get_token(self, flags: str) -> dict:
bin_rcode = "0b" + flags
rcode_value = int(bin_rcode, 2)

rcode_token = ""
match rcode_value:
case 0:
rcode_token = "Success"
case 1:
rcode_token = "Format specification error"
case 2:
rcode_token = "Server Failure"
case 3:
rcode_token = "Query does not exist in domain"
case 4:
rcode_token = "Type is not supported by the server"
case 5:
rcode_token = "Nonexecution of queries by server due to policy reasons"

return { 'value': rcode_value, 'meaning': rcode_token }

def hex_to_bin(hex_string: str) -> str:
return bin(int(hex_string, 16))[2:].zfill(16)

def parse_flags(flags: str) -> dict:
opcode = OpcodeToken()
rcode = rCodeToken()
# pseudo-index flags: 0 1234 5 6 7 8 910 1234
# Example flags: 0 0000 0 0 1 0 010 0000
data = {
'qr': {'value': 1, 'meaning': "response"} if int(flags[0]) == 1 else {'value': 0, 'meaning': "request"},
'opcode': opcode.get_token(flags[1:5]),
'authoritative_answer': {'value': 1, 'meaning': "authoritative"} if int(flags[5]) == 1 else {'value': 0, 'meaning': "non-authoritative"},
'truncation': {'value': 1, 'meaning': "Truncated"} if int(flags[6]) == 1 else {'value': 0, 'meaning': "Not truncated"},
'recursion_desired': {'value': 1, 'meaning': "Recursion requested"} if int(flags[7]) == 1 else {'value': 0, 'meaning': "Recursion not requested"},
'recursion_available': {'value': 1, 'meaning': "Recursion available"} if int(flags[8]) == 1 else {'value': 0, 'meaning': "Recursion not available"},
'zero': {'value': 0, 'meaning': "Reserved"} if int(flags[9:12], 2) == 0 else {'value': int(flags[9:12], 2), 'meaning': "Error (should be zero)"},
'rcode': rcode.get_token(flags[12:16])
}

return data

def pretty_print(header: Header, tokenized_flags, binary_flags, query):
print("\nDNS Query Analysis")
print("=" * 60)
print(f"Base Query: {query}")
print("\nDNS Header")
print("=" * 60)
print(f"Transaction ID: 0x{header.id.hex()} (bytes: {header.id})")
print("=" * 60)
print("Counts:")
print(f" Questions: {header.question_count}")
print(f" Answers: {header.answer_count}")
print(f" Authority RRs: {header.authority_count}")
print(f" Additional RRs: {header.additional_records_count}")
print("=" * 60)
print("")
print("Tokenized Flags:")
print("=" * 60)
print(f"Binary Flags: {binary_flags}")

for key, value in tokenized_flags.items():
print(f"{key.replace('_', ' ').title():20}: {value['value']} - {value['meaning']}")

print("=" * 60)

def parser(query: str):
flags_substring = query[4:8]
binary_flags = hex_to_bin(flags_substring)
tokenized_flags = parse_flags(binary_flags)

flags = Flags(
qr=tokenized_flags['qr']['value'],
opcode=tokenized_flags['opcode']['value'],
authoritative_answer=tokenized_flags['authoritative_answer']['value'],
truncation=tokenized_flags['truncation']['value'],
recursion_desired=tokenized_flags['recursion_desired']['value'],
recursion_available=tokenized_flags['recursion_available']['value'],
zero=tokenized_flags['zero']['value'],
response_code=tokenized_flags['rcode']['value']
)
header = Header(
id=bytes.fromhex(query[0:4]),
flags=flags,
question_count=int(query[8:12], 16),
answer_count=int(query[12:16], 16),
authority_count=int(query[16:20], 16),
additional_records_count=int(query[20:24], 16)
)

pretty_print(header, tokenized_flags, binary_flags, query)
Empty file added src/records/types.py
Empty file.
Empty file added src/resolver/cache.py
Empty file.
Empty file added src/resolver/resolver.py
Empty file.
10 changes: 10 additions & 0 deletions src/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import socket
from message import parser

server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("127.0.0.1", 53053))

while True:
(client_socket, address) = server_socket.recvfrom(1024)
parser.parser(client_socket.hex())
print()

0 comments on commit d97ec03

Please sign in to comment.