Поиск активных узлов в локальной сети с помощью Python

Я создаю Messenger, который аналогичен IP Messenger в Python 2.7 и Windows.

Я хочу использовать ту же функциональность, что и IP Messenger для поиска систем с одинаковым программным обеспечением по локальной сети, но я не могу понять этот метод.

Может кто-нибудь, пожалуйста, помогите мне решить проблему поиска IP-адреса компьютера или имени хоста, на котором выполняется одно и то же программное обеспечение в локальной сети с использованием Python 2.7 и библиотеки сокетов.

Пожалуйста, предложите что-то, что можно реализовать в Windows, а не Nmap (только для Linux), и это будет очень полезно, если решением будет код библиотеки сокетов Python.


person aki92    schedule 17.03.2013    source источник
comment
Я предполагаю, что это будет похоже на использование nmap для обнаружения хостов в локальной сети, а затем попытка подключения к определенному порту на заданных хостах. Этот поток SO может быть полезен stackoverflow .com/questions/166506/   -  person Ifthikhan    schedule 17.03.2013
comment
Nmap, о котором вы говорите, является программным обеспечением Python-Nmap или Nmap, и, пожалуйста, не могли бы вы рассказать о nmap, который я могу реализовать в Windows   -  person aki92    schedule 18.03.2013


Ответы (4)


Команда " net view " командной строки Windows решила мою проблему, изложенную в вопросе.

С помощью этой команды я могу найти все компьютеры, подключенные к моему компьютеру через локальную сеть, а затем я отправлю пакеты на все компьютеры, и компьютеры, отвечающие на мой пакет, будут системами, на которых работает то же программное обеспечение, что и я, что полностью решило мою проблему.

Этот код перечисляет имена всех компьютеров, подключенных к моему компьютеру через локальную сеть.

import os
os.system('net view > conn.tmp')
f = open('conn.tmp', 'r')
f.readline();f.readline();f.readline()

conn = []
host = f.readline()
while host[0] == '\\':
    conn.append(host[2:host.find(' ')])
    host = f.readline()

print conn
f.close()    
person aki92    schedule 19.03.2013
comment
Ваше решение отличное, но у меня возникает ошибка при попытке добавить conn.tmp в папку, отформатированную следующим образом: 2013-11-22 - MyProjectName из-за - (с пробелами) У вас есть решение этой проблемы? - person RPDeshaies; 22.11.2013
comment
@ Tareck117 Попробуйте изменить имя папки, так как кажется, что проблема только в папке. - person aki92; 29.11.2013

Что вы хотите сделать, так это пропинговать локальную сеть для живых узлов. Что-то вроде этого скрипта с использованием Scapy может быть достаточно. Эта реализация на чистом Python может быть еще одной более легкой альтернативой.

Чтобы получить текущий IP-адрес, вы можете следовать одному из решений, приведенных в этот вопрос.

Расширение вышеупомянутого класса Ping может позволить вам получать результаты обратно для чтения:

# TODO: This is a quick hack to retrieve the results 
# of the ping, you should probably do something a bit more elegant here!
class PingQuery(Ping):
    def __init__():
        super().__init__()
        result = false

    def print_success(self, delay, ip, packet_size, ip_header, icmp_header):
        result = ip

Затем вы можете просмотреть адреса в подсети, чтобы найти список активных машин:

subnet = "192.168.0." # TODO: Trim the last number off the IP address retrieved earlier
for i in range(1, 255):
    hostname = subnet + i
    p = PingQuery(hostname, 500, 55) # Timeout after 500ms per node
    p.run(1)
    if (p.result):
        print p.result + " is live"

После этого вы можете опрашивать работающие машины, пытаясь подключиться к каждой машине с выбранным вами портом, проверяя наличие специально созданного TCP-пакета, который доказывает, что прослушивающая программа на самом деле является вашим программным обеспечением.

person seanhodges    schedule 17.03.2013
comment
Я хочу что-то, что может работать на окнах. Ваше первое решение ограничено Linux, а второй код принимает ввод в качестве имени хоста, а затем проверяет, существует ли имя хоста или нет, но мне нужна функциональность, в которой, если я запускаю код и подключаюсь к 5 компьютерам по локальной сети, тогда он должен перечислить все IP-адрес и код 5 компьютеров не должны принимать никаких входных данных. - person aki92; 18.03.2013
comment
Код в моем первом предложении не ограничивается Linux, он поддерживает все платформы, которые Scapy поддерживает. Вторая ссылка представляет собой альтернативу Scapy, но вам все равно нужно будет скопировать логику цикла сканирования из окрестности.py. - person seanhodges; 18.03.2013
comment
Я думаю, что решение моей проблемы может быть очень небольшим, так как мне просто нужно сделать то, что я указал в своем комментарии выше, например. из 5 комп., я думаю, что назначение статического IP-адреса для ethernet может решить мою проблему, но все же я не могу ее решить. - person aki92; 18.03.2013
comment
Nmap — это полнофункциональная программа обнаружения сети и аудита безопасности, а python-nmap — оболочка Python для этой программы. Если установка стороннего программного обеспечения для вас не проблема, то это может быть хорошим подходом. Не забудьте добавить свой собственный ответ на этот вопрос и пометить его как решенный, как только вы найдете работоспособное решение. Если у вас возникли проблемы с использованием python-nmap, задайте новый вопрос, чтобы другие могли найти его и помочь вам. - person seanhodges; 18.03.2013
comment
Если вам нужно интегрированное решение, которое просто использует переносимые библиотеки Python, я все же рекомендую мое предложение выше. Есть несколько деталей, которые нужно сгладить, но приведенные выше примеры доказывают, что это можно сделать. - person seanhodges; 18.03.2013
comment
Большое спасибо, seanhodges, за помощь, и моя проблема была решена с помощью команды net view. - person aki92; 19.03.2013
comment
Ничего страшного, рад, что у вас все получилось. Теперь вы можете пометить свой ответ как решенный — просто нажмите на галочку слева от своего ответа. Это поможет другим людям с похожим вопросом быстрее найти решение. - person seanhodges; 19.03.2013

На основании ответа aki92...

import re
import subprocess
# ...

nodes = re.findall(r'\\(.+?)(?: .*)?\n',subprocess.check_output('net view'))
person frmdstryr    schedule 22.01.2015

Добавлен http-уровень с ответом JSON с использованием Simple HttpServer.

import time
import socket
import struct
import select
import random
import json
import asyncore
from netaddr import IPNetwork
import BaseHTTPServer


# From /usr/include/linux/icmp.h; your milage may vary.
ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris.

ICMP_CODE = socket.getprotobyname('icmp')
ERROR_DESCR = {
    1: ' - Note that ICMP messages can only be '
       'sent from processes running as root.',
    10013: ' - Note that ICMP messages can only be sent by'
           ' users or processes with administrator rights.'
    }

__all__ = ['create_packet', 'do_one', 'verbose_ping', 'PingQuery',
           'multi_ping_query']


HOST_NAME = '0.0.0.0' # !!!REMEMBER TO CHANGE THIS!!!
PORT_NUMBER = 9000 # Maybe set this to 9000.
SUBNET = '10.10.20.1/24'
host_list = []
"""
Below class would handle all rest requests
"""
class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
     def do_HEAD(self,s):
         s.send_response(200)
         s.send_header("Content-type", "text/html")
         s.end_headers()
     def do_GET(s):
        """Respond to a GET request."""  
        s.send_response(200)
        s.send_header("content_disposition", "attachment; filename=serverstatus.json")
        s.send_header("Content-type", "text/json")
        s.end_headers()

        for ip in IPNetwork(SUBNET):
            host_list.append(ip.format(None))

        #Create Object of SubnetMonitor
        monitor =  SubnetMonitor()

        responseData = {}
        for host, ping in monitor.multi_ping_query(host_list).iteritems():

            if ping is not None:
                print(host,'()',socket.gethostbyname(host)   , '=', ping)   
                responseData[host] = 'Is Up'    
            else:
                responseData[host] = 'Is Down'

        json_data = json.dumps(responseData, sort_keys=True, indent=4, separators=(',', ': '))

        s.wfile.write(json_data)


"""
Below class is used to send/receive all ping/icmp requests 
"""     
class PingQuery(asyncore.dispatcher):


    def __init__(self, host, p_id, timeout=0.5, ignore_errors=False,monitor=None):
        """
       Derived class from "asyncore.dispatcher" for sending and
       receiving an icmp echo request/reply.

       Usually this class is used in conjunction with the "loop"
       function of asyncore.

       Once the loop is over, you can retrieve the results with
       the "get_result" method. Assignment is possible through
       the "get_host" method.

       "host" represents the address under which the server can be reached.
       "timeout" is the interval which the host gets granted for its reply.
       "p_id" must be any unique integer or float except negatives and zeros.

       If "ignore_errors" is True, the default behaviour of asyncore
       will be overwritten with a function which does just nothing.

       """
        self.monitor = monitor
        asyncore.dispatcher.__init__(self)
        try:
            self.create_socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
        except socket.error as e:
            if e.errno in ERROR_DESCR:
                # Operation not permitted
                raise socket.error(''.join((e.args[1], ERROR_DESCR[e.errno])))
            raise # raise the original error
        self.time_received = 0
        self.time_sent = 0
        self.timeout = timeout
        # Maximum for an unsigned short int c object counts to 65535 so
        # we have to sure that our packet id is not greater than that.
        self.packet_id = int((id(timeout) / p_id) % 65535)
        self.host = host
        self.packet = self.monitor.create_packet(self.packet_id)
        if ignore_errors:
            # If it does not care whether an error occured or not.
            self.handle_error = self.do_not_handle_errors
            self.handle_expt = self.do_not_handle_errors

    def writable(self):
        return self.time_sent == 0

    def handle_write(self):
        self.time_sent = time.time()
        while self.packet:
            # The icmp protocol does not use a port, but the function
            # below expects it, so we just give it a dummy port.
            sent = self.sendto(self.packet, (self.host, 1))
            self.packet = self.packet[sent:]

    def readable(self):
        # As long as we did not sent anything, the channel has to be left open.
        if (not self.writable()
            # Once we sent something, we should periodically check if the reply
            # timed out.
            and self.timeout < (time.time() - self.time_sent)):
            self.close()
            return False
        # If the channel should not be closed, we do not want to read something
        # until we did not sent anything.
        return not self.writable()

    def handle_read(self):
        read_time = time.time()
        packet, addr = self.recvfrom(1024)
        header = packet[20:28]
        type, code, checksum, p_id, sequence = struct.unpack("bbHHh", header)
        if p_id == self.packet_id:
            # This comparison is necessary because winsocks do not only get
            # the replies for their own sent packets.
            self.time_received = read_time
            self.close()

    def get_result(self):
        """Return the ping delay if possible, otherwise None."""
        if self.time_received > 0:
            return self.time_received - self.time_sent

    def get_host(self):
        """Return the host where to the request has or should been sent."""
        return self.host

    def do_not_handle_errors(self):
        # Just a dummy handler to stop traceback printing, if desired.
        pass

    def create_socket(self, family, type, proto):
        # Overwritten, because the original does not support the "proto" arg.
        sock = socket.socket(family, type, proto)
        sock.setblocking(0)
        self.set_socket(sock)
        # Part of the original but is not used. (at least at python 2.7)
        # Copied for possible compatiblity reasons.
        self.family_and_type = family, type

    # If the following methods would not be there, we would see some very
    # "useful" warnings from asyncore, maybe. But we do not want to, or do we?
    def handle_connect(self):
        pass

    def handle_accept(self):
        pass

    def handle_close(self):
        self.close()

class SubnetMonitor:

    def __init__(self):
        print("Subnet Monitor Started")     

    def checksum(self,source_string):
        # I'm not too confident that this is right but testing seems to
        # suggest that it gives the same answers as in_cksum in ping.c.
        sum = 0
        count_to = (len(source_string) / 2) * 2
        count = 0
        while count < count_to:
            this_val = ord(source_string[count + 1])*256+ord(source_string[count])
            sum = sum + this_val
            sum = sum & 0xffffffff # Necessary?
            count = count + 2
        if count_to < len(source_string):
            sum = sum + ord(source_string[len(source_string) - 1])
            sum = sum & 0xffffffff # Necessary?
        sum = (sum >> 16) + (sum & 0xffff)
        sum = sum + (sum >> 16)
        answer = ~sum
        answer = answer & 0xffff
        # Swap bytes. Bugger me if I know why.
        answer = answer >> 8 | (answer << 8 & 0xff00)
        return answer


    def create_packet(self,id):
        """Create a new echo request packet based on the given "id"."""
        # Header is type (8), code (8), checksum (16), id (16), sequence (16)
        header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
        data = 192 * 'Q'
        # Calculate the checksum on the data and the dummy header.
        my_checksum = self.checksum(header + data)
        # Now that we have the right checksum, we put that in. It's just easier
        # to make up a new header than to stuff it into the dummy.
        header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0,
                             socket.htons(my_checksum), id, 1)
        return header + data


    def do_one(self,dest_addr, timeout=1):
        """
        Sends one ping to the given "dest_addr" which can be an ip or hostname.
        "timeout" can be any integer or float except negatives and zero.
        Returns either the delay (in seconds) or None on timeout and an invalid
        address, respectively.
        """
        try:
            my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
        except socket.error as e:
            if e.errno in ERROR_DESCR:
                # Operation not permitted
                raise socket.error(''.join((e.args[1], ERROR_DESCR[e.errno])))
            raise # raise the original error
        try:
            host = socket.gethostbyname(dest_addr)
        except socket.gaierror:
            return
        # Maximum for an unsigned short int c object counts to 65535 so
        # we have to sure that our packet id is not greater than that.
        packet_id = int((id(timeout) * random.random()) % 65535)
        packet = self.create_packet(packet_id)
        while packet:
            # The icmp protocol does not use a port, but the function
            # below expects it, so we just give it a dummy port.
            sent = my_socket.sendto(packet, (dest_addr, 1))
            packet = packet[sent:]
        delay = self.receive_ping(my_socket, packet_id, time.time(), timeout)
        my_socket.close()
        return delay


    def receive_ping(self,my_socket, packet_id, time_sent, timeout):
        # Receive the ping from the socket.
        time_left = timeout
        while True:
            started_select = time.time()
            ready = select.select([my_socket], [], [], time_left)
            how_long_in_select = time.time() - started_select
            if ready[0] == []: # Timeout
                return
            time_received = time.time()
            rec_packet, addr = my_socket.recvfrom(1024)
            icmp_header = rec_packet[20:28]
            type, code, checksum, p_id, sequence = struct.unpack(
                'bbHHh', icmp_header)
            if p_id == packet_id:
                return time_received - time_sent
            time_left -= time_received - time_sent
            if time_left <= 0:
                return


    def verbose_ping(self,dest_addr, timeout=2, count=4):
        """
        Sends one ping to the given "dest_addr" which can be an ip or hostname.
        "timeout" can be any integer or float except negatives and zero.
        "count" specifies how many pings will be sent.
        Displays the result on the screen.

        """
        for i in range(count):
            print('ping {}...'.format(dest_addr))
            delay = self.do_one(dest_addr, timeout)
            if delay == None:
                print('failed. (Timeout within {} seconds.)'.format(timeout))
            else:
                delay = round(delay * 1000.0, 4)
                print('get ping in {} milliseconds.'.format(delay))
        print('')


    def multi_ping_query(self,hosts, timeout=1, step=512, ignore_errors=False):
        """
        Sends multiple icmp echo requests at once.
        "hosts" is a list of ips or hostnames which should be pinged.
        "timeout" must be given and a integer or float greater than zero.
        "step" is the amount of sockets which should be watched at once.
        See the docstring of "PingQuery" for the meaning of "ignore_erros".
        """
        results, host_list, id = {}, [], 0
        for host in hosts:
            try:
                host_list.append(socket.gethostbyname(host))
            except socket.gaierror:
                results[host] = None
        while host_list:
            sock_list = []
            for ip in host_list[:step]: # select supports only a max of 512
                id += 1
                sock_list.append(PingQuery(ip, id, timeout, ignore_errors,self))
                host_list.remove(ip)
            # Remember to use a timeout here. The risk to get an infinite loop
            # is high, because noone can guarantee that each host will reply!
            asyncore.loop(timeout)
            for sock in sock_list:
                results[sock.get_host()] = sock.get_result()
        return results      


if __name__ == '__main__':
     server_class = BaseHTTPServer.HTTPServer
     httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
     print time.asctime(), "Server Starts - %s:%s" % (HOST_NAME, PORT_NUMBER)
     try:
         httpd.serve_forever()
     except KeyboardInterrupt:
         pass
     httpd.server_close()
     print time.asctime(), "Server Stops - %s:%s" % (HOST_NAME, PORT_NUMBER)
person Ashwin Rayaprolu    schedule 13.02.2017