diff --git a/paused.conf b/paused.conf new file mode 100644 index 0000000..06abb83 --- /dev/null +++ b/paused.conf @@ -0,0 +1,17 @@ + +# resume information +resume-index = 604 +seed = 10352559912596683105 +rate = 100 +shard = 1/1 +nocapture = servername + +output-filename = /dev/stdout +output-format = ndjson + +adapter-ip = 172.16.0.3 +# TARGET SELECTION (IP, PORTS, EXCLUDES) +ports = 80,443 +range = 0.0.0.0-1.1.1.0 +range = 1.1.1.2-255.255.255.255 + diff --git a/ptrstream.go b/ptrstream.go index 6a0efb7..b86de16 100644 --- a/ptrstream.go +++ b/ptrstream.go @@ -520,6 +520,7 @@ func main() { 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)") loop := flag.Bool("l", false, "Loop continuously after completion") + jsonOutput := flag.Bool("j", false, "Output NDJSON to stdout (no TUI)") flag.Parse() shardNum, totalShards, err := parseShardArg(*shard) @@ -670,6 +671,97 @@ func main() { } }() + if *jsonOutput { + // JSON-only mode + jobs := make(chan string, cfg.concurrency) + var wg sync.WaitGroup + + // Start workers + for i := 0; i < cfg.concurrency; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for ip := range jobs { + var response DNSResponse + var err error + var server string + + if len(cfg.dnsServers) > 0 { + response, server, err = lookupWithRetry(ip, cfg) + if idx := strings.Index(server, ":"); idx != -1 { + server = server[:idx] + } + } else { + names, err := net.LookupAddr(ip) + if err == nil { + response = DNSResponse{Names: names, RecordType: "PTR"} + } + } + + if err != nil || len(response.Names) == 0 { + continue + } + + ptr := "" + for _, name := range response.Names { + if cleaned := strings.TrimSpace(strings.TrimSuffix(name, ".")); cleaned != "" { + ptr = cleaned + break + } + } + + if ptr == "" { + continue + } + + record := struct { + Seen string `json:"seen"` + IP string `json:"ip"` + Nameserver string `json:"nameserver"` + Record string `json:"record"` + RecordType string `json:"record_type"` + TTL uint32 `json:"ttl"` + }{ + Seen: time.Now().Format(time.RFC3339), + IP: ip, + Nameserver: server, + Record: response.Target, + RecordType: response.RecordType, + TTL: response.TTL, + } + + if response.RecordType != "CNAME" { + record.Record = ptr + } + + if data, err := json.Marshal(record); err == nil { + fmt.Println(string(data)) + } + } + }() + } + + // Feed IPs to workers + for { + stream, err := golcg.IPStream("0.0.0.0/0", shardNum, totalShards, int(*seed), nil) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating IP stream: %v\n", err) + return + } + + for ip := range stream { + jobs <- ip + } + + if !cfg.loop { + break + } + } + close(jobs) + wg.Wait() + return + } + jobs := make(chan string, cfg.concurrency) go func() { @@ -746,23 +838,26 @@ func writeNDJSON(cfg *Config, timestamp time.Time, ip, server, ptr, recordType, } record := struct { - Timestamp string `json:"timestamp"` - IPAddr string `json:"ip_addr"` - DNSServer string `json:"dns_server"` - PTRRecord string `json:"ptr_record"` + Seen string `json:"seen"` + IP string `json:"ip"` + Nameserver string `json:"nameserver"` + Record string `json:"record"` RecordType string `json:"record_type"` - Target string `json:"target,omitempty"` TTL uint32 `json:"ttl"` }{ - Timestamp: timestamp.Format(time.RFC3339), - IPAddr: ip, - DNSServer: server, - PTRRecord: ptr, + Seen: timestamp.Format(time.RFC3339), + IP: ip, + Nameserver: server, + Record: target, // For CNAME records, use the target RecordType: recordType, - Target: target, TTL: ttl, } + // If it's not a CNAME, use the PTR record + if recordType != "CNAME" { + record.Record = ptr + } + if data, err := json.Marshal(record); err == nil { cfg.mu.Lock() cfg.outputFile.Write(data)