Python: Получить локальный IP-адрес, используемый для отправки IP-данных на определенный удаленный IP-адрес - PullRequest
10 голосов
/ 07 сентября 2011

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

Решения должны работать по крайней мере на Windows XP и выше, предпочтительно на Mac OS X, так как это моя платформа разработки.

Первый шаг - получить IP-адрес любого из моих интерфейсов. В настоящее время я использую общепринятый подход для разрешения имени хоста машины:

def get_local_address():
    return socket.gethostbyname(socket.gethostname())

Это работает только иногда. В настоящее время он возвращает 172.16.249.1, который является адресом виртуального интерфейса, используемого VM Ware, так что это проблема.

Намного лучше было бы получить IP-адрес интерфейса по умолчанию, который в большинстве случаев должен быть правильным.

Еще лучше было бы получить фактический IP-адрес, используемый для соединения с сервером по протоколу, ориентированному на соединение, например TCP. Я действительно могу получить этот адрес, но не без попытки открыть соединение:

def get_address_to_connect_to(server_addr):
    non_open_port = 50000
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        s.connect((server_addr, non_open_port))
    except socket.error:
        pass
    else:
        s.close() # just in case

    return s.getsockname()[0]

Это будет блокировать до истечения некоторого времени ожидания TCP в случае, если сервер недоступен или злой межсетевой экран между ними блокирует пакеты ICMP. Есть ли лучший способ получить эту информацию?

Ответы [ 2 ]

12 голосов
/ 07 сентября 2011

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

Единственное решение, которое я смог найти, которое надежно работает на разных платформах, - это попытаться установить соединение, как и вы. Одним из улучшений в вашем коде является использование UDP вместо TCP, то есть SOCK_DGRAM вместо SOCK_STREAM. Это позволяет избежать тайм-аута подключения, и функция сразу завершается.

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

Редактировать: вот пример кода, который я использовал:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

try:
    s.connect((host, 9))
    client = s.getsockname()[0]
except socket.error:
    client = "Unknown IP"
finally:
    del s
return client

Я думаю, что вы получаете 0.0.0.0, потому что закрываете сокет перед вызовом getsockname. Другой совет - мое использование порта 9; это порт сброса RFC863 UDP, и поэтому он немного менее странный, чем какой-то случайный порт с высоким номером.

0 голосов
/ 07 сентября 2011

Должна ли отправляющая сторона знать свой IP-адрес?

Может ли клиент просто прослушивать данные, на которые он пытается подписаться (на всех интерфейсах) после отправки UDP-регистра, и сервер просто начинает отправлять данные на адрес, с которого произошла датаграмма?

...