Repository updated to Golang version
This commit is contained in:
parent
6b22805e2f
commit
8951577210
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Binary
|
||||
ptrstream
|
||||
ptrstream.exe
|
||||
|
||||
# Output files
|
||||
*.json
|
||||
*.txt
|
||||
|
||||
# Go specific
|
||||
*.test
|
||||
*.out
|
||||
/vendor/
|
||||
|
||||
# IDE specific
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS specific
|
||||
.DS_Store
|
||||
Thumbs.db
|
15
LICENSE
Normal file
15
LICENSE
Normal file
@ -0,0 +1,15 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2025, acidvegas <acid.vegas@acid.vegas>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
46
README.md
46
README.md
@ -1,29 +1,39 @@
|
||||
# PTR Stream
|
||||
|
||||
The ptrstream repository contains a straightforward yet well-crafted Python script for conducting reverse DNS lookups across the entire IPv4 address range. It systematically generates each IPv4 address in a pseudo-random sequence using a seed, ensuring every possible address is covered. For each IP address, the script performs a PTR *(reverse DNS)* lookup and logs all successful findings. Designed to run continuously, ptrstream is an efficient tool for network monitoring and tracking PTR records globally, making it a practical resource for network enthusiasts and professionals who require a reliable and uncomplicated solution for DNS monitoring.
|
||||
PTR Stream is a high-performance reverse DNS *(PTR record)* lookup tool written in Go. It efficiently processes the entire IPv4 address space, performing concurrent DNS lookups with support for custom DNS servers, output logging, and real-time progress visualization.
|
||||
|
||||
## Requirements
|
||||
- [python](https://www.python.org/)
|
||||
- [aiodns](https://pypi.org/project/aiodns/) *(pip install aiodns)*
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go install github.com/acidvegas/ptrstream@latest
|
||||
```
|
||||
|
||||
Or, build from source:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/acidvegas/ptrstream
|
||||
cd ptrstream
|
||||
go build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python ptrstream.py [options]
|
||||
ptrstream [options]
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
| --------------------- | ------------------------------------------------------------ |
|
||||
| `-c`, `--concurrency` | Control the speed of lookups. *(Default = 100)* |
|
||||
| `-t`, `--timeout` | Timeout for DNS lookups. *(Default = 5s)* |
|
||||
| `-r`, `--resolvers` | File containing DNS servers to use for lookups. *(Optional)* |
|
||||
| `-rt`, `--retries` | Number of times to retry a DNS lookup *(Default = 3)* |
|
||||
| `-s`, `--seed` | Seed to use for the random number generator. |
|
||||
###### Command Line Arguments
|
||||
| Flag | Description | Default | Example |
|
||||
|---------|--------------------------------------|---------|------------------------|
|
||||
| `-c` | Concurrency level | `100` | `-c 200` |
|
||||
| `-t` | Timeout for DNS queries | `2s` | `-t 5s` |
|
||||
| `-r` | Number of retries for failed lookups | `2` | `-r 3` |
|
||||
| `-dns` | File containing DNS servers | | `-dns nameservers.txt` |
|
||||
| `-debug`| Show unsuccessful lookups | `False` | `-debug` |
|
||||
| `-o` | Path to NDJSON output file | | `-o results.json` |
|
||||
| `-s` | Seed for IP generation | Random | `-s 12345` |
|
||||
| `-shard`| Shard specification | | `-shard 1/4` |
|
||||
|
||||
## Preview
|
||||
![](.screens/preview.gif)
|
||||
---
|
||||
|
||||
___
|
||||
|
||||
###### Mirrors
|
||||
[acid.vegas](https://git.acid.vegas/ptrstream) • [GitHub](https://github.com/acidvegas/ptrstream) • [GitLab](https://gitlab.com/acidvegas/ptrstream) • [SuperNETs](https://git.supernets.org/acidvegas/ptrstream)
|
||||
###### Mirrors: [acid.vegas](https://git.acid.vegas/ptrstream) • [SuperNETs](https://git.supernets.org/acidvegas/ptrstream) • [GitHub](https://github.com/acidvegas/ptrstream) • [GitLab](https://gitlab.com/acidvegas/ptrstream) • [Codeberg](https://codeberg.org/acidvegas/ptrstream)
|
||||
|
108
arpa-stream.py
108
arpa-stream.py
@ -1,108 +0,0 @@
|
||||
#/usr/bin/env python
|
||||
# arpa stream - developed by acidvegas in python (https://git.acid.vegas/ptrstream)
|
||||
|
||||
'''
|
||||
I have no idea where we are going with this, but I'm sure it'll be fun...
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import random
|
||||
|
||||
try:
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
raise ImportError('missing required \'dnspython\' library (pip install dnspython)')
|
||||
|
||||
|
||||
class colors:
|
||||
axfr = '\033[34m'
|
||||
error = '\033[31m'
|
||||
success = '\033[32m'
|
||||
ns_query = '\033[33m'
|
||||
ns_zone = '\033[36m'
|
||||
reset = '\033[0m'
|
||||
|
||||
|
||||
def genip() -> str:
|
||||
'''Generate a random IP address with 1 to 4 octets.'''
|
||||
num_octets = random.randint(1, 4)
|
||||
ip_parts = [str(random.randint(0, 255)) for _ in range(num_octets)]
|
||||
return '.'.join(ip_parts)
|
||||
|
||||
|
||||
def query_ns_records(ip: str) -> list:
|
||||
'''
|
||||
Query NS records for a given IP.
|
||||
|
||||
:param ip: The IP address to query NS records for.
|
||||
'''
|
||||
try:
|
||||
ns_records = [str(record.target)[:-1] for record in dns.resolver.resolve(f'{ip}.in-addr.arpa', 'NS')]
|
||||
if ns_records:
|
||||
print(f'{colors.ns_zone}Queried NS records for {ip}: {ns_records}{colors.reset}')
|
||||
return ns_records
|
||||
except Exception as e:
|
||||
print(f'{colors.error}Error querying NS records for {ip}: {e}{colors.reset}')
|
||||
return []
|
||||
|
||||
|
||||
def resolve_ns_to_ip(ns_hostname: str) -> list:
|
||||
'''
|
||||
Resolve NS hostname to IP.
|
||||
|
||||
:param ns_hostname: The NS hostname to resolve.
|
||||
'''
|
||||
try:
|
||||
ns_ips = [ip.address for ip in dns.resolver.resolve(ns_hostname, 'A')]
|
||||
if ns_ips:
|
||||
print(f'{colors.ns_query}Resolved NS hostname {ns_hostname} to IPs: {ns_ips}{colors.reset}')
|
||||
return ns_ips
|
||||
except Exception as e:
|
||||
print(f'{colors.error}Error resolving NS {ns_hostname}: {e}{colors.reset}')
|
||||
return []
|
||||
|
||||
|
||||
def axfr_check(ip: str, ns_ip: str):
|
||||
'''
|
||||
Perform AXFR check on a specific nameserver IP.
|
||||
|
||||
:param ip: The IP address to perform the AXFR check on.
|
||||
:param ns_ip: The nameserver IP to perform the AXFR check on.
|
||||
'''
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.nameservers = [ns_ip]
|
||||
try:
|
||||
if resolver.resolve(f'{ip}.in-addr.arpa', 'AXFR'):
|
||||
print(f'{colors.success}[SUCCESS]{colors.reset} AXFR on {ns_ip} for {ip}.in-addr.arpa')
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f'{colors.error}[FAIL]{colors.reset} AXFR on {ns_ip} for {ip}.in-addr.arpa - Error: {e}')
|
||||
return False
|
||||
|
||||
|
||||
def process_ip(ip: str):
|
||||
'''
|
||||
Process each IP: Fetch NS records and perform AXFR check.
|
||||
|
||||
:param ip: The IP address to process.
|
||||
'''
|
||||
for ns_hostname in query_ns_records(ip):
|
||||
for ns_ip in resolve_ns_to_ip(ns_hostname):
|
||||
if axfr_check(ip, ns_ip):
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='DNS AXFR Check Script')
|
||||
parser.add_argument('--concurrency', type=int, default=100, help='Number of concurrent workers')
|
||||
args = parser.parse_args()
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
|
||||
futures = {executor.submit(process_ip, genip()): ip for ip in range(args.concurrency)}
|
||||
while True:
|
||||
done, _ = concurrent.futures.wait(futures, return_when=concurrent.futures.FIRST_COMPLETED)
|
||||
for future in done:
|
||||
future.result() # We don't need to store the result as it's already printed
|
||||
futures[executor.submit(process_ip, genip())] = genip()
|
||||
futures = {future: ip for future, ip in futures.items() if future not in done}
|
19
go.mod
Normal file
19
go.mod
Normal file
@ -0,0 +1,19 @@
|
||||
module ptrstream
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/acidvegas/golcg v1.0.1
|
||||
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.7.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/term v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
52
go.sum
Normal file
52
go.sum
Normal file
@ -0,0 +1,52 @@
|
||||
github.com/acidvegas/golcg v1.0.1 h1:u6Ba3NZb7ssW0PIszl5B02OImOwICz4u4ljz+oUHSTU=
|
||||
github.com/acidvegas/golcg v1.0.1/go.mod h1:fMerl4iGjfD6Rar2xwARQgFRsy+ONuDNS0T85vBqEAE=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc=
|
||||
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592 h1:YIJ+B1hePP6AgynC5TcqpO0H9k3SSoZa2BGyL6vDUzM=
|
||||
github.com/rivo/tview v0.0.0-20241103174730-c76f7879f592/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
584
ptrstream.go
Normal file
584
ptrstream.go
Normal file
@ -0,0 +1,584 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/acidvegas/golcg"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
concurrency int
|
||||
timeout time.Duration
|
||||
retries int
|
||||
dnsServers []string
|
||||
serverIndex int
|
||||
debug bool
|
||||
outputFile *os.File
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
processed uint64
|
||||
total uint64
|
||||
lastProcessed uint64
|
||||
lastCheckTime time.Time
|
||||
success uint64
|
||||
failed uint64
|
||||
speedHistory []float64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *Stats) increment() {
|
||||
atomic.AddUint64(&s.processed, 1)
|
||||
}
|
||||
|
||||
func (s *Stats) incrementSuccess() {
|
||||
atomic.AddUint64(&s.success, 1)
|
||||
}
|
||||
|
||||
func (s *Stats) incrementFailed() {
|
||||
atomic.AddUint64(&s.failed, 1)
|
||||
}
|
||||
|
||||
func (c *Config) getNextServer() string {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if len(c.dnsServers) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
server := c.dnsServers[c.serverIndex]
|
||||
c.serverIndex = (c.serverIndex + 1) % len(c.dnsServers)
|
||||
return server
|
||||
}
|
||||
|
||||
func loadDNSServers(filename string) ([]string, error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var servers []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
server := strings.TrimSpace(scanner.Text())
|
||||
if server != "" && !strings.HasPrefix(server, "#") {
|
||||
if !strings.Contains(server, ":") {
|
||||
server += ":53"
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
return nil, fmt.Errorf("no valid DNS servers found in file")
|
||||
}
|
||||
|
||||
return servers, scanner.Err()
|
||||
}
|
||||
|
||||
func lookupWithRetry(ip string, cfg *Config) ([]string, string, error) {
|
||||
server := cfg.getNextServer()
|
||||
if server == "" {
|
||||
return nil, "", fmt.Errorf("no DNS servers available")
|
||||
}
|
||||
|
||||
r := &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{
|
||||
Timeout: cfg.timeout,
|
||||
}
|
||||
return d.DialContext(ctx, "udp", server)
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < cfg.retries; i++ {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), cfg.timeout)
|
||||
names, err := r.LookupAddr(ctx, ip)
|
||||
cancel()
|
||||
|
||||
if err == nil {
|
||||
return names, server, nil
|
||||
}
|
||||
|
||||
if i < cfg.retries-1 {
|
||||
server = cfg.getNextServer()
|
||||
if server == "" {
|
||||
return nil, "", fmt.Errorf("no more DNS servers available")
|
||||
}
|
||||
r = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{
|
||||
Timeout: cfg.timeout,
|
||||
}
|
||||
return d.DialContext(ctx, "udp", server)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, "", fmt.Errorf("lookup failed after %d retries", cfg.retries)
|
||||
}
|
||||
|
||||
func reverse(ss []string) []string {
|
||||
reversed := make([]string, len(ss))
|
||||
for i, s := range ss {
|
||||
reversed[len(ss)-1-i] = s
|
||||
}
|
||||
return reversed
|
||||
}
|
||||
|
||||
func colorizeIPInPtr(ptr, ip string) string {
|
||||
specialHosts := []string{"localhost", "undefined.hostname.localhost", "unknown"}
|
||||
for _, host := range specialHosts {
|
||||
if strings.EqualFold(ptr, host) {
|
||||
return "[gray]" + ptr
|
||||
}
|
||||
}
|
||||
|
||||
octets := strings.Split(ip, ".")
|
||||
|
||||
patterns := []string{
|
||||
strings.ReplaceAll(ip, ".", "\\."),
|
||||
strings.Join(reverse(strings.Split(ip, ".")), "\\."),
|
||||
strings.ReplaceAll(ip, ".", "-"),
|
||||
strings.Join(reverse(strings.Split(ip, ".")), "-"),
|
||||
}
|
||||
|
||||
zeroPadded := make([]string, 4)
|
||||
for i, octet := range octets {
|
||||
zeroPadded[i] = fmt.Sprintf("%03d", parseInt(octet))
|
||||
}
|
||||
patterns = append(patterns,
|
||||
strings.Join(zeroPadded, "-"),
|
||||
strings.Join(reverse(zeroPadded), "-"),
|
||||
)
|
||||
|
||||
pattern := strings.Join(patterns, "|")
|
||||
re := regexp.MustCompile("(" + pattern + ")")
|
||||
|
||||
matches := re.FindAllStringIndex(ptr, -1)
|
||||
if matches == nil {
|
||||
return "[green]" + ptr
|
||||
}
|
||||
|
||||
var result strings.Builder
|
||||
lastEnd := 0
|
||||
|
||||
for _, match := range matches {
|
||||
if match[0] > lastEnd {
|
||||
result.WriteString("[green]")
|
||||
result.WriteString(ptr[lastEnd:match[0]])
|
||||
}
|
||||
result.WriteString("[aqua]")
|
||||
result.WriteString(ptr[match[0]:match[1]])
|
||||
lastEnd = match[1]
|
||||
}
|
||||
|
||||
if lastEnd < len(ptr) {
|
||||
result.WriteString("[green]")
|
||||
result.WriteString(ptr[lastEnd:])
|
||||
}
|
||||
|
||||
finalResult := result.String()
|
||||
finalResult = strings.ReplaceAll(finalResult, ".in-addr.arpa", ".[blue]in-addr.arpa")
|
||||
finalResult = strings.ReplaceAll(finalResult, ".gov", ".[red]gov")
|
||||
finalResult = strings.ReplaceAll(finalResult, ".mil", ".[red]mil")
|
||||
|
||||
return finalResult
|
||||
}
|
||||
|
||||
func parseInt(s string) int {
|
||||
num := 0
|
||||
fmt.Sscanf(s, "%d", &num)
|
||||
return num
|
||||
}
|
||||
|
||||
const maxBufferLines = 1000
|
||||
|
||||
func worker(jobs <-chan string, wg *sync.WaitGroup, cfg *Config, stats *Stats, textView *tview.TextView, app *tview.Application) {
|
||||
defer wg.Done()
|
||||
for ip := range jobs {
|
||||
var names []string
|
||||
var server string
|
||||
var err error
|
||||
timestamp := time.Now()
|
||||
|
||||
if len(cfg.dnsServers) > 0 {
|
||||
names, server, err = lookupWithRetry(ip, cfg)
|
||||
if idx := strings.Index(server, ":"); idx != -1 {
|
||||
server = server[:idx]
|
||||
}
|
||||
} else {
|
||||
names, err = net.LookupAddr(ip)
|
||||
}
|
||||
|
||||
stats.increment()
|
||||
|
||||
if err != nil {
|
||||
stats.incrementFailed()
|
||||
if cfg.debug {
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
errMsg := err.Error()
|
||||
if idx := strings.LastIndex(errMsg, ": "); idx != -1 {
|
||||
errMsg = errMsg[idx+2:]
|
||||
}
|
||||
debugLine := fmt.Sprintf("[gray]%s[-] [purple]%15s[-] [gray]│[-] [red]%s[-]\n",
|
||||
timestamp,
|
||||
ip,
|
||||
errMsg)
|
||||
app.QueueUpdateDraw(func() {
|
||||
fmt.Fprint(textView, debugLine)
|
||||
textView.ScrollToEnd()
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
stats.incrementFailed()
|
||||
if cfg.debug {
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
debugLine := fmt.Sprintf("[gray]%s[-] [purple]%15s[-] [gray]│[-] [red]No PTR record[-]\n",
|
||||
timestamp,
|
||||
ip)
|
||||
app.QueueUpdateDraw(func() {
|
||||
fmt.Fprint(textView, debugLine)
|
||||
textView.ScrollToEnd()
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
stats.incrementSuccess()
|
||||
|
||||
ptr := ""
|
||||
for _, name := range names {
|
||||
if cleaned := strings.TrimSpace(strings.TrimSuffix(name, ".")); cleaned != "" {
|
||||
ptr = cleaned
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ptr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
writeNDJSON(cfg, timestamp, ip, server, ptr)
|
||||
|
||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
var line string
|
||||
if len(cfg.dnsServers) > 0 {
|
||||
line = fmt.Sprintf("[gray]%s[-] [purple]%15s[-] [gray]│[-] [yellow]%15s[-] [gray]│[-] %s\n",
|
||||
timeStr,
|
||||
ip,
|
||||
server,
|
||||
colorizeIPInPtr(ptr, ip))
|
||||
} else {
|
||||
line = fmt.Sprintf("[gray]%s[-] [purple]%15s[-] [gray]│[-] %s\n",
|
||||
timeStr,
|
||||
ip,
|
||||
colorizeIPInPtr(ptr, ip))
|
||||
}
|
||||
|
||||
app.QueueUpdateDraw(func() {
|
||||
fmt.Fprint(textView, line)
|
||||
content := textView.GetText(false)
|
||||
lines := strings.Split(content, "\n")
|
||||
if len(lines) > maxBufferLines {
|
||||
newContent := strings.Join(lines[len(lines)-maxBufferLines:], "\n")
|
||||
textView.Clear()
|
||||
fmt.Fprint(textView, newContent)
|
||||
}
|
||||
textView.ScrollToEnd()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func parseShardArg(shard string) (int, int, error) {
|
||||
if shard == "" {
|
||||
return 1, 1, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(shard, "/")
|
||||
if len(parts) != 2 {
|
||||
return 0, 0, fmt.Errorf("invalid shard format (expected n/total)")
|
||||
}
|
||||
|
||||
shardNum, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid shard number: %v", err)
|
||||
}
|
||||
|
||||
totalShards, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid total shards: %v", err)
|
||||
}
|
||||
|
||||
if shardNum < 1 || shardNum > totalShards {
|
||||
return 0, 0, fmt.Errorf("shard number must be between 1 and total shards")
|
||||
}
|
||||
|
||||
return shardNum, totalShards, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
concurrency := flag.Int("c", 100, "Concurrency level")
|
||||
timeout := flag.Duration("t", 2*time.Second, "Timeout for DNS queries")
|
||||
retries := flag.Int("r", 2, "Number of retries for failed lookups")
|
||||
dnsFile := flag.String("dns", "", "File containing DNS servers (one per line)")
|
||||
debug := flag.Bool("debug", false, "Show unsuccessful lookups")
|
||||
outputPath := flag.String("o", "", "Path to NDJSON output file")
|
||||
seed := flag.Int64("s", 0, "Seed for IP generation (0 for random)")
|
||||
shard := flag.String("shard", "", "Shard specification (e.g., 1/4 for first shard of 4)")
|
||||
flag.Parse()
|
||||
|
||||
shardNum, totalShards, err := parseShardArg(*shard)
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing shard argument: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if *seed == 0 {
|
||||
*seed = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
concurrency: *concurrency,
|
||||
timeout: *timeout,
|
||||
retries: *retries,
|
||||
debug: *debug,
|
||||
}
|
||||
|
||||
if *outputPath != "" {
|
||||
f, err := os.OpenFile(*outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening output file: %v\n", err)
|
||||
return
|
||||
}
|
||||
cfg.outputFile = f
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
if *dnsFile != "" {
|
||||
servers, err := loadDNSServers(*dnsFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading DNS servers: %v\n", err)
|
||||
return
|
||||
}
|
||||
cfg.dnsServers = servers
|
||||
fmt.Printf("Loaded %d DNS servers\n", len(servers))
|
||||
}
|
||||
|
||||
app := tview.NewApplication()
|
||||
|
||||
textView := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetScrollable(true).
|
||||
SetChangedFunc(func() {
|
||||
app.Draw()
|
||||
})
|
||||
textView.SetBorder(true).SetTitle(" PTR Records ")
|
||||
|
||||
progress := tview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetTextAlign(tview.AlignLeft)
|
||||
progress.SetBorder(true).SetTitle(" Progress ")
|
||||
|
||||
flex := tview.NewFlex().
|
||||
SetDirection(tview.FlexRow).
|
||||
AddItem(textView, 0, 1, false).
|
||||
AddItem(progress, 3, 0, false)
|
||||
|
||||
stats := &Stats{
|
||||
total: 1 << 32,
|
||||
lastCheckTime: time.Now(),
|
||||
}
|
||||
|
||||
go func() {
|
||||
const movingAverageWindow = 5
|
||||
stats.speedHistory = make([]float64, 0, movingAverageWindow)
|
||||
stats.lastCheckTime = time.Now()
|
||||
|
||||
for {
|
||||
processed := atomic.LoadUint64(&stats.processed)
|
||||
success := atomic.LoadUint64(&stats.success)
|
||||
failed := atomic.LoadUint64(&stats.failed)
|
||||
|
||||
now := time.Now()
|
||||
duration := now.Sub(stats.lastCheckTime).Seconds()
|
||||
|
||||
if duration >= 1.0 {
|
||||
stats.mu.Lock()
|
||||
speed := float64(processed-stats.lastProcessed) / duration
|
||||
stats.speedHistory = append(stats.speedHistory, speed)
|
||||
if len(stats.speedHistory) > movingAverageWindow {
|
||||
stats.speedHistory = stats.speedHistory[1:]
|
||||
}
|
||||
|
||||
var avgSpeed float64
|
||||
for _, s := range stats.speedHistory {
|
||||
avgSpeed += s
|
||||
}
|
||||
avgSpeed /= float64(len(stats.speedHistory))
|
||||
|
||||
stats.lastProcessed = processed
|
||||
stats.lastCheckTime = now
|
||||
stats.mu.Unlock()
|
||||
|
||||
percent := float64(processed) / float64(stats.total) * 100
|
||||
|
||||
app.QueueUpdateDraw(func() {
|
||||
var width int
|
||||
_, _, width, _ = progress.GetInnerRect()
|
||||
if width == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
statsText := fmt.Sprintf(" [aqua]Count:[:-] [white]%s [gray]│[-] [aqua]Progress:[:-] [darkgray]%7.2f%%[-] [gray]│[-] [aqua]Rate:[:-] %s [gray]│[-] [aqua]Successful:[:-] [green]✓%s [-][darkgray](%5.1f%%)[-] [gray]│[-] [aqua]Failed:[:-] [red]✗%s [-][darkgray](%5.1f%%)[-] ",
|
||||
formatNumber(processed),
|
||||
percent,
|
||||
colorizeSpeed(avgSpeed),
|
||||
formatNumber(success),
|
||||
float64(success)/float64(processed)*100,
|
||||
formatNumber(failed),
|
||||
float64(failed)/float64(processed)*100)
|
||||
|
||||
barWidth := width - visibleLength(statsText) - 2
|
||||
filled := int(float64(barWidth) * (percent / 100))
|
||||
if filled > barWidth {
|
||||
filled = barWidth
|
||||
}
|
||||
|
||||
bar := strings.Builder{}
|
||||
bar.WriteString(statsText)
|
||||
bar.WriteString("[")
|
||||
bar.WriteString(strings.Repeat("█", filled))
|
||||
bar.WriteString(strings.Repeat("░", barWidth-filled))
|
||||
bar.WriteString("]")
|
||||
|
||||
progress.Clear()
|
||||
fmt.Fprint(progress, bar.String())
|
||||
})
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
stream, err := golcg.IPStream("0.0.0.0/0", shardNum, totalShards, int(*seed), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating IP stream: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
jobs := make(chan string, cfg.concurrency)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < cfg.concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go worker(jobs, &wg, cfg, stats, textView, app)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for ip := range stream {
|
||||
jobs <- ip
|
||||
}
|
||||
close(jobs)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
app.Stop()
|
||||
}()
|
||||
|
||||
if err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func formatNumber(n uint64) string {
|
||||
s := fmt.Sprint(n)
|
||||
parts := make([]string, 0)
|
||||
for i := len(s); i > 0; i -= 3 {
|
||||
start := i - 3
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
parts = append([]string{s[start:i]}, parts...)
|
||||
}
|
||||
formatted := strings.Join(parts, ",")
|
||||
|
||||
totalWidth := len(fmt.Sprint(1<<32)) + 3
|
||||
for len(formatted) < totalWidth {
|
||||
formatted = " " + formatted
|
||||
}
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
func colorizeSpeed(speed float64) string {
|
||||
switch {
|
||||
case speed >= 500:
|
||||
return fmt.Sprintf("[green]%5.0f/s[-]", speed)
|
||||
case speed >= 350:
|
||||
return fmt.Sprintf("[yellow]%5.0f/s[-]", speed)
|
||||
case speed >= 200:
|
||||
return fmt.Sprintf("[orange]%5.0f/s[-]", speed)
|
||||
case speed >= 100:
|
||||
return fmt.Sprintf("[red]%5.0f/s[-]", speed)
|
||||
default:
|
||||
return fmt.Sprintf("[gray]%5.0f/s[-]", speed)
|
||||
}
|
||||
}
|
||||
|
||||
func visibleLength(s string) int {
|
||||
noColors := regexp.MustCompile(`\[[a-zA-Z:-]*\]`).ReplaceAllString(s, "")
|
||||
return len(noColors)
|
||||
}
|
||||
|
||||
func writeNDJSON(cfg *Config, timestamp time.Time, ip, server, ptr string) {
|
||||
if cfg.outputFile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
record := struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
IPAddr string `json:"ip_addr"`
|
||||
DNSServer string `json:"dns_server"`
|
||||
PTRRecord string `json:"ptr_record"`
|
||||
}{
|
||||
Timestamp: timestamp.Format(time.RFC3339),
|
||||
IPAddr: ip,
|
||||
DNSServer: server,
|
||||
PTRRecord: ptr,
|
||||
}
|
||||
|
||||
if data, err := json.Marshal(record); err == nil {
|
||||
cfg.mu.Lock()
|
||||
cfg.outputFile.Write(data)
|
||||
cfg.outputFile.Write([]byte("\n"))
|
||||
cfg.mu.Unlock()
|
||||
}
|
||||
}
|
154
ptrstream.py
154
ptrstream.py
@ -1,154 +0,0 @@
|
||||
#/usr/bin/env python
|
||||
# ptrstream - developed by acidvegas in python (https://git.acid.vegas/ptrstream)
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
import aiodns
|
||||
except ImportError:
|
||||
raise ImportError('missing required \'aiodns\' library (pip install aiodns)')
|
||||
|
||||
# Do not store these in the results file
|
||||
bad_hosts = ['localhost','undefined.hostname.localhost','unknown']
|
||||
|
||||
# Colors
|
||||
class colors:
|
||||
ip = '\033[35m'
|
||||
ip_match = '\033[96m' # IP address mfound within PTR record
|
||||
ptr = '\033[93m'
|
||||
red = '\033[31m' # .gov or .mil indicator
|
||||
invalid = '\033[90m'
|
||||
reset = '\033[0m'
|
||||
grey = '\033[90m'
|
||||
|
||||
|
||||
def get_dns_servers() -> list:
|
||||
'''Get a list of DNS servers to use for lookups.'''
|
||||
source = urllib.request.urlopen('https://public-dns.info/nameservers.txt')
|
||||
results = source.read().decode().split('\n')
|
||||
return [server for server in results if ':' not in server]
|
||||
|
||||
|
||||
async def rdns(semaphore: asyncio.Semaphore, ip_address: str, resolver: aiodns.DNSResolver):
|
||||
'''
|
||||
Perform a reverse DNS lookup on an IP address.
|
||||
|
||||
:param semaphore: The semaphore to use for concurrency.
|
||||
:param ip_address: The IP address to perform a reverse DNS lookup on.
|
||||
'''
|
||||
async with semaphore:
|
||||
reverse_name = ipaddress.ip_address(ip_address).reverse_pointer
|
||||
try:
|
||||
answer = await resolver.query(reverse_name, 'PTR')
|
||||
if answer.name not in bad_hosts and answer.name != ip_address and answer.name != reverse_name:
|
||||
return ip_address, answer.name, True
|
||||
else:
|
||||
return ip_address, answer.name, False
|
||||
except aiodns.error.DNSError as e:
|
||||
if e.args[0] == aiodns.error.ARES_ENOTFOUND:
|
||||
return ip_address, f'{colors.red}No rDNS found{colors.reset}', False
|
||||
elif e.args[0] == aiodns.error.ARES_ETIMEOUT:
|
||||
return ip_address, f'{colors.red}DNS query timed out{colors.reset}', False
|
||||
else:
|
||||
return ip_address, f'{colors.red}DNS error{colors.grey} ({e.args[1]}){colors.reset}', False
|
||||
except Exception as e:
|
||||
return ip_address, f'{colors.red}Unknown error{colors.grey} ({str(e)}){colors.reset}', False
|
||||
|
||||
|
||||
def rig(seed: int) -> str:
|
||||
'''
|
||||
Random IP generator.
|
||||
|
||||
:param seed: The seed to use for the random number generator.
|
||||
'''
|
||||
max_value = 256**4
|
||||
random.seed(seed)
|
||||
for _ in range(max_value):
|
||||
shuffled_index = random.randint(0, max_value - 1)
|
||||
ip = ipaddress.ip_address(shuffled_index)
|
||||
yield str(ip)
|
||||
|
||||
|
||||
def fancy_print(ip: str, result: str):
|
||||
'''
|
||||
Print the IP address and PTR record in a fancy way.
|
||||
|
||||
:param ip: The IP address.
|
||||
:param result: The PTR record.
|
||||
'''
|
||||
if result in ('127.0.0.1', 'localhost','undefined.hostname.localhost','unknown'):
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.grey}-> {result}{colors.reset}')
|
||||
else:
|
||||
if ip in result:
|
||||
result = result.replace(ip, f'{colors.ip_match}{ip}{colors.ptr}')
|
||||
elif (daship := ip.replace('.', '-')) in result:
|
||||
result = result.replace(daship, f'{colors.ip_match}{daship}{colors.ptr}')
|
||||
elif (revip := '.'.join(ip.split('.')[::-1])) in result:
|
||||
result = result.replace(revip, f'{colors.ip_match}{revip}{colors.ptr}')
|
||||
elif (revip := '.'.join(ip.split('.')[::-1]).replace('.','-')) in result:
|
||||
result = result.replace(revip, f'{colors.ip_match}{revip}{colors.ptr}')
|
||||
print(f'{colors.ip}{ip.ljust(15)}{colors.reset} {colors.grey}->{colors.reset} {colors.ptr}{result}{colors.reset}')
|
||||
|
||||
|
||||
async def main(args: argparse.Namespace):
|
||||
'''
|
||||
Generate random IPs and perform reverse DNS lookups.
|
||||
|
||||
:param args: The command-line arguments.
|
||||
'''
|
||||
if args.resolvers:
|
||||
if os.path.exists(args.resolvers):
|
||||
with open(args.resolvers) as file:
|
||||
dns_resolvers = [item.strip() for item in file.read().splitlines()]
|
||||
else:
|
||||
raise FileNotFoundError(f'could not find file \'{args.resolvers}\'')
|
||||
else:
|
||||
dns_resolvers = get_dns_servers()
|
||||
dns_resolvers = random.shuffle(dns_resolvers)
|
||||
|
||||
resolver = aiodns.DNSResolver(nameservers=dns_resolvers, timeout=args.timeout, tries=args.retries, rotate=True)
|
||||
semaphore = asyncio.Semaphore(args.concurrency)
|
||||
|
||||
tasks = []
|
||||
results_cache = []
|
||||
|
||||
seed = random.randint(10**9, 10**10 - 1) if not args.seed else args.seed
|
||||
ip_generator = rig(seed)
|
||||
|
||||
for ip in ip_generator:
|
||||
if len(tasks) < args.concurrency:
|
||||
task = asyncio.create_task(rdns(semaphore, ip, resolver))
|
||||
tasks.append(task)
|
||||
else:
|
||||
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
tasks = list(pending)
|
||||
for task in done:
|
||||
ip, result, success = task.result()
|
||||
if result:
|
||||
fancy_print(ip, result)
|
||||
if success:
|
||||
results_cache.append(f'{ip}:{result}')
|
||||
if len(results_cache) >= 1000:
|
||||
stamp = time.strftime('%Y%m%d')
|
||||
with open(f'ptr_{stamp}_{seed}.txt', 'a') as file:
|
||||
file.writelines(f"{record}\n" for record in results_cache)
|
||||
results_cache = []
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Perform asynchronous reverse DNS lookups.')
|
||||
parser.add_argument('-c', '--concurrency', type=int, default=100, help='Control the speed of lookups.')
|
||||
parser.add_argument('-t', '--timeout', type=int, default=5, help='Timeout for DNS lookups.')
|
||||
parser.add_argument('-r', '--resolvers', type=str, help='File containing DNS servers to use for lookups.')
|
||||
parser.add_argument('-rt', '--retries', type=int, default=3, help='Number of times to retry a DNS lookup.')
|
||||
parser.add_argument('-s', '--seed', type=int, help='Seed to use for random number generator.')
|
||||
args = parser.parse_args()
|
||||
|
||||
asyncio.run(main(args))
|
Loading…
Reference in New Issue
Block a user