Как удаленно обнаружить экземпляры SQL Server на машине? Часть III

Содержание предыдущей серии.

Третьим способом обнаружения является SQL Browser Service, которую мы в меру пристально разберем в данной части. Как известно, до версии 2000 на машину могло было быть установлено не более одной штуки SQL Server, который принимал SQL-запросы, возвращал результаты и вообще общался с клиентами сугубо через ТСР-порт 1433, назначенный ему IANA, коль скоро речь идет о TDS поверх ТСР/IP. Все было статично и просто. Когда на машину стало возможным устанавливать несколько экземпляров SQL Server, экземпляру по умолчанию по-прежнему присваивается порт 1433, а остальным они по умолчанию раздаются динамически при старте экземпляра. Посмотреть порт, по которому слушает тот или иной экземпляр, можно, например, в ErrorLog’e:

Рис. 1

Соответственно, соединиться с выбранным SQL Server’ом можно, указав в строке соединения его порт:

Рис. 2

Это означает, что мы хотим соединиться не с экземпляром MSSQLSERVER, а с экземпляром по имени SQLEXPRESS на той же машине (см. Рис. 4 предыдущего поста). Префикс tcp: указывает протокол, после запятой указывается порт. Аналогичные формы возможны для других протоколов: например, np:hostname или np:\\hostname\pipe\pipeName для Named Pipes, lpc:hostname для Shared Memory, rpc:hostname или rpc:hostname, protocol для Multiprotocol – см. КВ 313295.

Не укажи мы в данном случае порт, нас по умолчанию бы вынесло на экземпляр MSSQLSERVER (с одноименным названием сервиса). Имя MSSQLSERVER всегда является названием экземпляра по умолчанию. Извне к нему можно обращаться по имени машины. Прочие экземпляры являются именованными, и к ним нужно обращаться в формате <имя машины>\<имя экземпляра>, например, w7x86sql08r2\SQLEXPRESS – это обращение к экземпляру по имени SQLEXPRESS (в первом посте этой серии мы его видели по имени сервиса MSSQL$SQLEXPRESS), либо в явной форме, как показано на Рис.2.

В случае явной формы все понятно. Вопрос возникает при обращении типа w7x86sql08r2\SQLEXPRESS. Откуда в этом случае происходит знание, по какому порту с ним общаться? Тем более, что нам ничего не мешает в любой момент поменять порт для любого экземпляра, включая дефолтный, как описано в BOL:

Рис. 3

Можно назначить другой статический порт или сказать, чтобы порт назначался динамически; после перезапуска экземпляра изменение вступит в силу. Для разруливания ситуации с портами был придуман SQL Server Resolution Protocol, который повесили на UDP-порт 1434 и первоначально реализовали внутри службы SQL Server, а начиная с 2000 SP3a выделили в отдельную службу под названием SQL Server Browser. Когда она поднимается, то первым делом лезет в реджистри и читает уже знакомый нам по предыдущему посту ключ HKLM\SOFTWARE\Microsoft\Microsoft SQL Server. Для каждого экземпляра в HKLM\SOFTWARE\Microsoft\Microsoft SQL Server\<ключ экземпляра>\MSSQLServer\SuperSocketNetLib\Tcp хранится информация о том, по какому порту он в данный момент слушает:

Рис. 4

Видно, что это практически 1:1 Рис.3. Эту информацию служба SQL Server Browser сообщает всем обратившимся к ней (по UDP 1434) желающим. Давайте посмотрим, что творится на 1434, когда удаленная машина хочет установить соединение с каким-нибудь экземпляром. Как на грех, на удаленной машине ничего нет, даже SSMS не установлена, а код писать лень. Ну ничего, я через Excel зайду, а на SQL Serverной тачке в этот момент отснифферим порт UDP 1434. Кстати, в июне вышла свежая версия Microsoft Network Monitor 3.4.

Рис. 5

Рис. 6

Очень успешно соединяемся и видим, что на порте 1434 тем временем ничего не происходит. Полный голяк:

Рис. 7

Это происходит потому, что когда мы устанавливаем соединение с экземпляром по умолчанию, порт которого (1433) не менялся, службе SQL Server Browser нет работы – все и так понятно. Изменим на Рис.5 название экземпляра на какой-нибудь именованый инстанс, в моем случае SQLEXPRESS и попробуем соединиться

Рис. 8

Во, поперла фишка. Ssrp – это как раз упоминавшийся выше SQL Server Resolution Protocol. В других снифферах и сетевых анализаторах он может проходить под вывеской Microsoft-SQL-Monitor (ms-sql-m), как определено в IANA.

Рис. 9

Чтобы этот протокол по-человечески разбирался, в Network Monitor надо включить парсер Windows:

Рис. 10

Клиент (Excel) шлет на UDP 1434 пакеты PacketType: CLNT_UCAST_INST, 4 (0x04), передавая дополнительно название экземпляра, чтобы SQL Server Browser ответил ему, по какому порту с этим экземпляром общаться. Это замечательно, но для выполнения нашей задачи хотелось бы получить от него зараз все имеющиеся на машине экземпляры вместе с портами, на которых они слушают. SQL Server Browser этим знанием из реестра обладает. Осталось понять, как попросить его им поделиться. В посте Протоколы Microsoft SQL Server я писал, что теперь у нас все протоколы выложены открыто и описаны, поэтому открываем описание [MC-SQLR]: SQL Server Resolution Protocol Specification и читаем в п.2.2, что валидные запросы по этому протоколу бывают CLNT_BCAST_EX, CLNT_UCAST_EX, CLNT_UCAST_INST и CLNT_UCAST_DAC.

CLNT_UCAST_INST мы только что видели – это запрос порта по имени экземпляра. CLNT_UCAST_DAC, аналогично, - это запрос порта для выделенного административного соединения, про которое я не распространялся, чтобы не запутывать, но вообще оно тоже висит на отдельном порте, обслуживается отдельным планировщиком, и посмотреть порт для него можно все там же, на Рис.1. Для SQL Express его, понятно, смотреть бессмысленно, потому что по умолчанию оно там задисейблено, чтобы не жрало ресурсы, и включить его можно флагом трассировки 7806 –см. Dedicated Admin Connection and SQL Express. CLNT_UCAST_EX – это то, что доктор прописал в нашем случае - CLNT_UCAST_EX packet is a unicast request that is generated by clients that are trying to identify the list of database instances and their network protocol connection information installed on a single machine, а CLNT_BCAST_EX – это то же самое, но в броадкасте вообще по всем машинам в подсети. Из п.4.1 узнаем, что все, что требуется сделать для формирования пакета CLNT_UCAST_EX, - это заслать на UDP 1434 трешку. Т.е. даже не два байта переслать, а один. За чем же дело стало, давайте зашлем? Вот скрипт:

[System.Net.Sockets.UdpClient] $udpClient = New-Object -TypeName System.Net.Sockets.UdpClient 
$udpClient.Connect("192.168.0.2", 1434) 
[Byte[]] $sendBytes = New-Object Byte[] 1 
$sendBytes[0] = 0x3 
$udpClient.Send($sendBytes, $sendBytes.Length) | Out-Null 
[System.Net.IPEndPoint] $remoteIpEndPoint = New-Object -TypeName System.Net.IPEndPoint -ArgumentList ([System.Net.IPAddress]::Any), 0 
[Byte[]] $receiveBytes = $udpClient.Receive([ref]$remoteIpEndPoint) 
[string] $returnData = [System.Text.Encoding]::ASCII.GetString($receiveBytes) 
Write-Host `n($remoteIpEndPoint.Address) : ($remoteIpEndPoint.Port) откликнулся следующим SVR_RESP:`n 
Write-Host $returnData `n 
$udpClient.Close()

Скрипт 1

Вот он даже работает:

Рис. 11

Разбор строки SVR_RESP и вывод в красивом виде экземпляров SQL Server вместе с их версиями, и протоколами предоставляется читателям в качестве самостоятельного упражнения. Формат SVR_RESP для CLNT_UCAST_EX описан в п.2.2.5 – первым байтом там всегда идет 0х5, потом два байта – длина сегмента RESP_DATA, ну и т.д.

Для работы данного способа требуется стартованная служба SQL Server Browser и открытый порт UDP 1434 на том компьютере, где притаились SQL Serverы. Чтобы экземпляр SQL Server не показывался наружу службой SQL Server Browser, надо на рис.3 кликнуть правой кнопкой на строчку Protocols for <имя требуемого экземпляра>, из открывшегося контекстного меню выбрать пункт Properties и в строке Hide Instance отметить Yes, как описывается в BOL. Для экземпляра можно оставить из протоколов только Shared Memory и общаться с ним только в пределах данной машины, никак не засвечивая наружу.

Рис. 12

Окончание следует.

Автор: Алексей Шуленин