Как отправить dns запрос через сокет
Перейти к содержимому

Как отправить dns запрос через сокет

  • автор:

Использование сокетов для отправки и получения данных по протоколу TCP

Перед использованием сокета для связи с удаленными устройствами необходимо инициализировать сокет, указав протокол и сведения о сетевом адресе. Конструктор класса Socket имеет параметры, которые определяют семейство адресов, тип сокета и тип протокола, которые сокет использует для подключения. При подключении сокета клиента к сокету сервера клиент будет использовать IPEndPoint объект для указания сетевого адреса сервера.

Создание конечной точки IP-адреса

При работе с System.Net.Socketsвы представляете конечную точку сети в IPEndPoint виде объекта . Создается IPEndPoint с соответствующим номером IPAddress порта. Прежде чем начать беседу с помощью Socket, вы создадите канал данных между приложением и удаленным назначением.

В качестве уникального идентификатора службы протокол TCP/IP использует сетевой адрес и номер порта службы. Сетевой адрес идентифицирует конкретное сетевое назначение; номер порта определяет конкретную службу на этом устройстве, к которому нужно подключиться. Сочетание сетевого адреса и порта службы называется конечной точкой, которая представлена в .NET классом EndPoint . Потомок определяется для каждого поддерживаемого EndPoint семейства адресов; для семейства IP-адресов классом является IPEndPoint.

Класс Dns предоставляет службы доменных имен для приложений, использующих интернет-службы TCP/IP. Метод GetHostEntryAsync запрашивает DNS-сервер для сопоставления понятного для пользователя доменного имени (например, «host.contoso.com») с числовым интернет-адресом (например 192.168.1.1 , ). GetHostEntryAsync возвращает объект Task , который при ожидании содержит список адресов и псевдонимов для запрошенного имени. В большинстве случаев можно использовать первый адрес из возвращенного массива AddressList. Следующий код получает объект , IPAddress содержащий IP-адрес сервера host.contoso.com .

IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("host.contoso.com"); IPAddress ipAddress = ipHostInfo.AddressList[0]; 

Для ручного тестирования и отладки обычно можно использовать GetHostEntryAsync метод , чтобы получить заданное Dns.GetHostName() значение для разрешения имени localhost в IP-адрес.

Центр интернет-номеров (IANA) определяет номера портов для общих служб. Дополнительные сведения см. в разделе IANA: реестр имен служб и номеров портов транспортных протоколов). Другие службы могут использовать номера портов в диапазоне от 1024 до 65535. Следующий код объединяет IP-адрес для host.contoso.com с номером порта, чтобы создать удаленную конечную точку для подключения.

IPEndPoint ipEndPoint = new(ipAddress, 11_000); 

После определения адреса удаленного устройства и выбора порта для подключения приложение может установить подключение к удаленному устройству.

Создание Socket клиента

Создав endPoint объект , создайте сокет клиента для подключения к серверу. После подключения сокета он может отправлять и получать данные из подключения к сокету сервера.

using Socket client = new( ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); await client.ConnectAsync(ipEndPoint); while (true) < // Send message. var message = "Hi friends ��!<|EOM|>"; var messageBytes = Encoding.UTF8.GetBytes(message); _ = await client.SendAsync(messageBytes, SocketFlags.None); Console.WriteLine($"Socket client sent message: \"\""); // Receive ack. var buffer = new byte[1_024]; var received = await client.ReceiveAsync(buffer, SocketFlags.None); var response = Encoding.UTF8.GetString(buffer, 0, received); if (response == "<|ACK|>") < Console.WriteLine( $"Socket client received acknowledgment: \"\""); break; > // Sample output: // Socket client sent message: "Hi friends ��!<|EOM|>" // Socket client received acknowledgment: "<|ACK|>" > client.Shutdown(SocketShutdown.Both); 

В приведенном выше коде C#:

  • Создает экземпляр нового Socket объекта с заданным endPoint семейством адресов экземпляров , SocketType.Streamи ProtocolType.Tcp.
  • Socket.ConnectAsync Вызывает метод с экземпляром в endPoint качестве аргумента.
  • В цикле while :
    • Кодирует и отправляет сообщение на сервер с помощью Socket.SendAsync.
    • Записывает отправленное сообщение в консоль.
    • Инициализирует буфер для получения данных с сервера с помощью Socket.ReceiveAsync.
    • response Когда является подтверждением, он записывается в консоль, и цикл завершается.

    Создание Socket сервера

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

    using Socket listener = new( ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listener.Bind(ipEndPoint); listener.Listen(100); var handler = await listener.AcceptAsync(); while (true) < // Receive message. var buffer = new byte[1_024]; var received = await handler.ReceiveAsync(buffer, SocketFlags.None); var response = Encoding.UTF8.GetString(buffer, 0, received); var eom = "<|EOM|>"; if (response.IndexOf(eom) > -1 /* is end of message */) < Console.WriteLine( $"Socket server received message: \"\""); var ackMessage = "<|ACK|>"; var echoBytes = Encoding.UTF8.GetBytes(ackMessage); await handler.SendAsync(echoBytes, 0); Console.WriteLine( $"Socket server sent acknowledgment: \"\""); break; > // Sample output: // Socket server received message: "Hi friends ��!" // Socket server sent acknowledgment: "<|ACK|>" > 

    В приведенном выше коде C#:

    • Создает экземпляр нового Socket объекта с заданным endPoint семейством адресов экземпляров , SocketType.Streamи ProtocolType.Tcp.
    • Вызывает listener Socket.Bind метод с экземпляром в endPoint качестве аргумента для связывания сокета с сетевым адресом.
    • Метод Socket.Listen() вызывается для прослушивания входящих подключений.
    • Вызывает listener метод для Socket.AcceptAsync принятия входящего подключения к сокету handler .
    • В цикле while :
      • Вызовы Socket.ReceiveAsync для получения данных от клиента.
      • При получении данных они декодируются и записываются в консоль.
      • response Если сообщение заканчивается на <|EOM|>, подтверждение отправляется клиенту с помощью Socket.SendAsync.

      Запуск примера клиента и сервера

      Сначала запустите серверное приложение, а затем запустите клиентское приложение.

      dotnet run --project socket-server Socket server starting. Found: 172.23.64.1 available on port 9000. Socket server received message: "Hi friends ��!" Socket server sent acknowledgment: "<|ACK|>" Press ENTER to continue. 

      Клиентское приложение отправит сообщение серверу, а сервер ответит подтверждением.

      dotnet run --project socket-client Socket client starting. Found: 172.23.64.1 available on port 9000. Socket client sent message: "Hi friends ��!<|EOM|>" Socket client received acknowledgment: "<|ACK|>" Press ENTER to continue. 

      См. также раздел

      Совместная работа с нами на GitHub

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

      Вопрос про systemd, сокеты и проблему с программным прослушиванием трафика для отказа от сокетов.

      Решил я автоматизировать процесс запуска dnscrypt-proxy с помощью systemd, ибо надоело после каждой перезагрузки запускать из консоли. Погуглил — нашел готовый шаблон — сделал. Вроде бы можно насладиться успехом — но нет, мне захотелось разобраться что это за конфиги и что они делают. Все шло успешно, пока я не споткнулся об одну теоретически решаемую проблему, при желании заменить целый конфиг на пару символов.

      dnscrypt-proxy — софт, цель которого в шифровании DNS трафика и его обменом со специальными DNS серверами.
      dnscrypt-proxy имеет функцию прослушивания трафика с помощью атрибута -a [ip:port]

      Итак, имеется есть два юнита: сокет dnscrypt-proxy.socket, который слушает IP 127.0.0.2:53(DNS трафик, в моем случае) и перенаправляет весь трафик на служебный юнит dnscrypt-proxy.service, который уже работает непосредственно с dnscrypt-proxy, если я правильно разобрался.

      Так вот, сам вопрос: почему команда dnscrypt-proxy -R dns.server.name -a 127.0.0.2:53 не равна записи ExecStart=/usr/sbin/dnscrypt-proxy -R dns.server.name -a 127.0.0.2:53 в конфиге? Иначе как объяснить, что первая работает, а вторая — нет?

      __________
      Вот как выглядят юниты:
      ——
      /etc/systemd/system/dnscrypt-proxy.service

      [Unit] Description=DNSCrypt client proxy Requires=dnscrypt-proxy.socket [Install] WantedBy=multi-user.target [Service] Type=simple NonBlocking=true ExecStart=/usr/sbin/dnscrypt-proxy -R dns.server.name

      ——
      /etc/systemd/system/dnscrypt-proxy.socket

      [Unit] Description=dnscrypt-proxy listening socket After=network.target [Socket] ListenStream=127.0.0.2:53 ListenDatagram=127.0.0.2:53 [Install] WantedBy=sockets.target

      __________
      letni ★
      01.05.16 13:43:57 MSK

      Ты не понимаешь, как работает socket-активация в systemd. Она требует специальной поддержки со стороны запускаемого приложения (в данном случае dnscrypt-proxy).

      Вкратце: на раннем этапе загрузки (ещё до монтирования ФС) systemd проходит по всем *.socket-файлам и сам открывает все перечисленные в них сокеты. Таким образом складывается впечатление, что программа уже работает и к ней можно подключаться, хотя на самом деле нет. Когда же в сокет в конце концов прилетают какие-то данные (кто-то хочет подключиться) — systemd не трогает эти данные, оставляя их во внутриядерном буфере, но запускает основную программу (*.service-файл). Дальше самое главное: эта программа должна специальным образом «забрать» у systemd уже открытый сокет, в котором уже есть какие-то данные, и обработать их.

      Соответственно, чтобы сказать программе, что она должна не сама открывать нужный сокет, а забирать из systemd уже готовый, мы используем немного другие параметры командной строки ( ExecStart=/usr/local/sbin/dnscrypt-proxy -R dnscrypt.eu-nl ).

      Если написать ExecStart=/usr/sbin/dnscrypt-proxy -R dns.server.name -a 127.0.0.2:53 , то dnscrypt-proxy сам попытается открыть сокет на порту 53 и обломается, потому что этот сокет уже был открыт самим systemd.

      В итоге есть два варианта:

      1. выкинуть *.socket-файл и указать порт в командной строке dnscrypt-proxy, со всеми вытекающими (dnscrypt-proxy будет запущен только в конце загрузки, что может быть неприемлемо для тебя — все программы, которые по стечению обстоятельств сделают DNS-запросы до запуска dnscrypt, либо обломаются, либо уйдут во второй по списку ресолвер, обходя dnscrypt, если у тебя в resolv.conf не только 127.0.0.1);
      2. оставить всё как есть, т. е. взять *.socket- и *.service-файлы из первоисточника (.socket, .service).

      intelfx ★★★★★
      ( 01.05.16 13:58:04 MSK )
      Последнее исправление: intelfx 01.05.16 14:05:26 MSK (всего исправлений: 3)

      Ответ на: комментарий от intelfx 01.05.16 13:58:04 MSK

      Ясно, спасибо. Я предполагал, что dnscrypt-proxy поддерживает сокет, который бы прослушивал IP.

      Но мне все равно не понятно, почему строка ExecStart=/usr/sbin/dnscrypt-proxy -R dns.server.name -a 127.0.0.2:53 не равна команде dnscrypt-proxy -R dns.server.name -a 127.0.0.2:53?

      Ведь в обоих случаях происходит обращение к dnscrypt-proxy с одинаковыми аргументами.

      letni ★
      ( 01.05.16 14:10:50 MSK ) автор топика
      Ответ на: комментарий от letni 01.05.16 14:10:50 MSK

      Я предполагал, что dnscrypt-proxy поддерживает сокет, который бы прослушивал IP.

      Ты правильно предполагал — dnscrypt-proxy умеет сам открывать IP-сокеты. Но в данном случае мы просим его этого не делать, т. к. уже сделано заранее.

      почему строка ExecStart=/usr/sbin/dnscrypt-proxy -R dns.server.name -a 127.0.0.2:53 не равна команде dnscrypt-proxy -R dns.server.name -a 127.0.0.2:53?

      Почему не равна? Вполне равна. Но чтобы это работало, необходимо заранее «освободить» порт 53, т. е. (в частности) отключить и остановить соответствующий .socket-файл. А также убрать из .service-юнита директиву Requires=dnscrypt-proxy.socket , иначе этот .socket будет автоматически запускаться и мешать.

      intelfx ★★★★★
      ( 01.05.16 14:16:12 MSK )
      Последнее исправление: intelfx 01.05.16 14:17:15 MSK (всего исправлений: 3)

      Ответ на: комментарий от intelfx 01.05.16 14:16:12 MSK

      Сделал, все работает 🙂

      [Unit] Description=DNSCrypt client proxy [Install] WantedBy=multi-user.target [Service] Type=simple NonBlocking=true ExecStart=/usr/sbin/dnscrypt-proxy -R dns.server.name -E -a 127.0.0.2

      Получается dnscrypt теперь запускается последним, потому что не указано после чего это делать, и приоритет самый низкий?

      letni ★
      ( 01.05.16 14:43:52 MSK ) автор топика
      Ответ на: комментарий от letni 01.05.16 14:43:52 MSK

      dnscrypt запускается «когда-нибудь», потому что не указано до чего это делать. А приоритет у всех одинаковый.

      Как перенаправить все исходящие интернет-запросы устройств на сокет на одном из устройств в локальной сети

      Доброго времени суток. Имеется ноутбук, роутер Asus RT-N53, и несколько устройств, подключённых к нему по Wi-Fi. Можно ли все исходящие интернет-запросы устройств (на любой веб-сервер, IP-адрес в интернете, и т.п. любого TCP-, UDP-порта и т.д.) перенаправить на сокет, запущенный на ноутбуке на порту x? То есть, если я открою веб-страницы в браузере по адресу, к примеру, www.google.com , 111.222.333.454:666 , test.abvgd.privet или localhost:5555 должна отобразиться веб-страница, которую предоставит мне сокет, запущенный на ноутбуке. Так же, при попытке установить соединение с сокетом по протоколу UDP, чтобы также в ответ получалась моя веб-страница с моего сокета (могу запустить сокет на обоих протоколах TCP и UDP). Как такое реализовать? DNS-сервер не в помощь, он только домены умеет «перенаправлять», т.е. если я зайду в браузере на веб-страницу www.yandex.ru , то сервер сможет перенаправить меня на сокет на ноутбуке в локальной сети, а если я наберу 77.88.55.55 (IP-адрес домена Яндекса), то DNS-сервер об этом запросе не узнает, и пользователь сможет посетить сайт Яндекса.

      Отслеживать
      задан 7 июн 2017 в 15:23
      2,510 5 5 золотых знаков 31 31 серебряный знак 60 60 бронзовых знаков

      Даже если роутер сможет это сделать, то как вы узнаете что это за запросы. для вас это будет просто набор непонятных данных, так как информации о протоколе по которому шел пакет и на какой адрес и порт он был направлен не сохранится (она же будет заменена на один единственный ваш адрес/порт), а следовательно вы не сможете сформировать такой ответ который ожидает получить клиент (он то ожидает получить ответ с того адреса куда посылал пакет, а вы то их не знаете). Это делается по другому, для этого существуют проксирующие протоколы, например socks.

      7 июн 2017 в 15:31

      Если вопрос стоит только в том, что это нужно сделать, тогда вам надо сделать на роутере (если он поддерживает) правило которое производит безусловный редирект для всех пакетов (кроме целевого ноутбука, иначе свои собственные пакеты тоже получите) в цепочке FORWARD на адрес вашего ноутбука. Но смысла в этом действительно немного, какую задачу вы хотите решить?

      7 июн 2017 в 15:34

      А да, если вы вдруг под словом «сокет» имели ввиду все таки socks (который к термину «сокет» не имеет ни малейшего отношения), то вам нужно копать в сторону прозрачного socks, что в прниципе на linux возможно, но в стандартные прошивки роутеров явно не входит

      7 июн 2017 в 15:37

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

      7 июн 2017 в 15:41

      Ну или как вариант, измените прошивку роутера, установив туда какой нибудь сниффер вроде tcpdump и будете консолью к нему подключаться и снимать данные с этого снифера

      Как направить DNS-запрос на другой сервер для отдельного диапазона IP при помощи iptables?

      Не могу сделать ping по имени ПК (который находится в сети на 1-2 уровня выше).
      Как можно добавить ещё один конкретный DNS сервер только для диапазона IP 192.168.5.x?
      Или только для доменов из одного слова (имя пк)?. Вообщем чтобы запросы на сайты так и остались на 8.8.8.8 , а запросы на Vasya-PC — слались на 192.168.0.6 например?

      • Вопрос задан более трёх лет назад
      • 495 просмотров

      10 комментариев

      Простой 10 комментариев

      hint000

      не надо разделять, все DNS-запросы отправляете на 192.168.0.6. То, что он может ответить самостоятельно (Vasya-PC) — отвечает. Что не может — 192.168.0.6 перенаправляет (forward) на 8.8.8.8, получает ответ и отдаёт этот ответ клиенту.

      chifth @chifth Автор вопроса

      hint000, если бы всё было так банально — я бы не уточнял что нужно указать днс для конкретного диапазона.
      Немного обрисую обстановку чтобы было понятно что и зачем.

      Работаю в офисе, получаю интернет по кабелю как и другие сотрудники.
      Притащил из дома роутер, чтобы раздать себе вайфай. Ну и попутно рекламу порезать через adblock и впн поднять. короче игрушка на OpenWRT.
      Но перестал работать сетевой принтер который на ПК другого сотрудника.
      На WAN интерфейсе ручками прописан DNS 8.8.8.8 (нужно для ВПН и Adblock)
      Поэтому мне и надо как-то выделить запросы из моей мини-сети 192.168.1.х на сеть фирмы 192.168.0.х и направить их на DNS фирмы 192.168.0.6.

      Пробовал server=/.domain.local/192.168.0.6 — не помогает почему-то.
      По IP — связь есть, но там динамика, поэтому принтер нужно по имени подключить. Вот так как-то

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *