mirror of
https://github.com/apache/httpd.git
synced 2026-10-23 22:49:01 +03:00
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1934905 13f79535-47bb-0310-9956-ffa450edef68
137 lines
5.3 KiB
Python
137 lines
5.3 KiB
Python
#!@pythonbin@
|
|
#
|
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership.
|
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
|
# (the "License"); you may not use this file except in compliance with
|
|
# the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
# logresolve.py -- Python port of the historical Perl logresolve.pl.
|
|
#
|
|
# usage: logresolve.py <infile >outfile
|
|
#
|
|
# input = Apache/NCSA/.. logfile with IP numbers at start of lines
|
|
# output = same logfile with IP addresses resolved to hostnames where
|
|
# name lookups succeeded.
|
|
#
|
|
# this differs from the C based 'logresolve' in that this script
|
|
# resolves a number (CHILDREN) of addresses concurrently and sets a
|
|
# short timeout (TIMEOUT) for each lookup in order to keep things moving
|
|
# quickly.
|
|
#
|
|
# The original Perl version forked CHILDREN subprocesses and exchanged
|
|
# IPs/hostnames over Unix sockets, with a per-lookup alarm(TIMEOUT).
|
|
# This Python port reproduces the *semantics* without the fork/socket
|
|
# IPC machinery: a concurrent.futures.ThreadPoolExecutor with
|
|
# max_workers=CHILDREN performs up to CHILDREN reverse lookups in
|
|
# parallel, and the per-lookup timeout is enforced via
|
|
# socket.setdefaulttimeout(TIMEOUT) (socket.gethostbyaddr() honours the
|
|
# process default socket timeout; it has no timeout argument of its own).
|
|
#
|
|
# Results are cached in a dict so each unique IP is resolved only once,
|
|
# and output lines are emitted in the exact same order as the input
|
|
# (logfiles must stay in order).
|
|
#
|
|
# Concurrency / timeout / order-preservation:
|
|
# * Concurrency: ThreadPoolExecutor(max_workers=CHILDREN). Each unique
|
|
# IP is submitted exactly once; the cache dict guards against
|
|
# resolving the same IP twice.
|
|
# * Timeout: socket.setdefaulttimeout(TIMEOUT) bounds each DNS lookup.
|
|
# As a belt-and-braces measure the worker also bounds itself via
|
|
# future.result(timeout=...) when collecting results.
|
|
# * Order: we buffer all input lines (remembering each line's leading
|
|
# IP), resolve the unique IPs concurrently, then walk the buffered
|
|
# lines in their original order substituting the cached hostname.
|
|
|
|
import socket
|
|
import sys
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
CHILDREN = 40
|
|
TIMEOUT = 5
|
|
|
|
|
|
def nslookup(ip):
|
|
"""Reverse-resolve an IP to a hostname.
|
|
|
|
Equivalent to the Perl gethostbyaddr(gethostbyname($ip), AF_INET).
|
|
Returns the resolved hostname, or the original IP on any failure or
|
|
timeout (matching the Perl behaviour of leaving the IP as-is).
|
|
"""
|
|
try:
|
|
hostname = socket.gethostbyaddr(ip)[0]
|
|
except Exception:
|
|
return ip
|
|
return hostname if hostname else ip
|
|
|
|
|
|
def main():
|
|
# Bound every DNS lookup to TIMEOUT seconds. socket.gethostbyaddr()
|
|
# has no timeout argument, so we rely on the process-wide default
|
|
# socket timeout, which it honours.
|
|
socket.setdefaulttimeout(TIMEOUT)
|
|
|
|
# Read the whole logfile, buffering each line and the IP that starts
|
|
# it. Order is preserved by replaying this buffer at the end.
|
|
lines = [] # list of (ip, rest, had_space); rest keeps its newline
|
|
unique_ips = [] # unique IPs in first-seen order (for stable submit)
|
|
seen = set()
|
|
|
|
for line in sys.stdin:
|
|
# split on the FIRST space only; a line with no space is all IP.
|
|
parts = line.split(' ', 1)
|
|
if len(parts) > 1:
|
|
ip = parts[0]
|
|
rest = parts[1]
|
|
had_space = True
|
|
else:
|
|
# No space: the whole line is the IP. Strip the trailing
|
|
# newline so it resolves cleanly, but remember the line
|
|
# ending so we can reproduce it verbatim on output.
|
|
stripped = line.rstrip('\n')
|
|
ip = stripped
|
|
rest = line[len(stripped):]
|
|
had_space = False
|
|
lines.append((ip, rest, had_space))
|
|
if ip not in seen:
|
|
seen.add(ip)
|
|
unique_ips.append(ip)
|
|
|
|
# Resolve all unique IPs concurrently, up to CHILDREN at a time.
|
|
cache = {}
|
|
if unique_ips:
|
|
with ThreadPoolExecutor(max_workers=CHILDREN) as pool:
|
|
futures = {ip: pool.submit(nslookup, ip) for ip in unique_ips}
|
|
for ip, fut in futures.items():
|
|
try:
|
|
cache[ip] = fut.result(timeout=TIMEOUT + 1)
|
|
except Exception:
|
|
# On timeout/failure leave the IP unchanged.
|
|
cache[ip] = ip
|
|
|
|
# Emit lines in the original input order, substituting hostnames.
|
|
out = sys.stdout
|
|
for ip, rest, had_space in lines:
|
|
host = cache.get(ip, ip)
|
|
if had_space:
|
|
out.write("%s %s" % (host, rest))
|
|
else:
|
|
# No space in the original line: emit host then the original
|
|
# line ending (rest is "\n", "" with EOF no-newline, etc.).
|
|
out.write(host)
|
|
if rest:
|
|
out.write(rest)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|