Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save lovasoa/940bc406434ee441866669243bb9ba42 to your computer and use it in GitHub Desktop.

Select an option

Save lovasoa/940bc406434ee441866669243bb9ba42 to your computer and use it in GitHub Desktop.
SQL Server Resolution Protocol (SSRP) Specification

SQL Server Resolution Protocol (SSRP) Specification

Source: https://learn.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/1ea6e25f-bff9-4364-ba21-5dc449a601b7

Protocol Overview

The SQL Server Resolution Protocol is a simple UDP-based application-layer protocol for discovering SQL Server database instances and retrieving their network connection endpoints. The protocol operates over UDP port 1434 and supports both unicast and broadcast/multicast communication patterns.

Transport

  • Protocol: UDP (RFC 768)
  • Port: 1434
  • Addressing: Supports IPv4 unicast/broadcast and IPv6 unicast/multicast
  • Security: No authentication, encryption, or reliability mechanisms

Message Format

All integer fields use little-endian byte order. Text strings use multibyte character set (MBCS) encoding based on the system code page. String matching is case-insensitive.

Client Request Messages

Message Type Byte Value Structure Purpose
CLNT_BCAST_EX 0x02 Single byte Broadcast/multicast request for all instances on network
CLNT_UCAST_EX 0x03 Single byte Unicast request for all instances on specific machine
CLNT_UCAST_INST 0x04 Byte + instance name + null Request for specific named instance
CLNT_UCAST_DAC 0x0F Byte + 0x01 + instance name + null Request for dedicated administrator connection port

CLNT_UCAST_INST structure: [0x04][InstanceName\0]

  • Instance name must be null-terminated MBCS string
  • Maximum 32 bytes excluding null terminator

CLNT_UCAST_DAC structure: [0x0F][0x01][InstanceName\0]

  • Protocol version byte is always 0x01
  • Instance name must be null-terminated MBCS string
  • Maximum 32 bytes excluding null terminator

Server Response Messages

SVR_RESP structure: [0x05][RESP_SIZE:2][RESP_DATA:variable]

  • SVR_RESP: Single byte with value 0x05
  • RESP_SIZE: 2-byte unsigned integer (little-endian) indicating RESP_DATA length in bytes
  • RESP_DATA: Variable-length MBCS string containing response information

Size limits:

  • CLNT_UCAST_INST responses: Maximum 1,024 bytes
  • CLNT_BCAST_EX and CLNT_UCAST_EX responses: Maximum 65,535 bytes
  • Per-instance information should not exceed 1,024 bytes

SVR_RESP (DAC) structure: [0x05][0x0006][0x01][TCP_PORT:2]

  • RESP_SIZE: Always 0x0006 (6 bytes total response)
  • Protocol version: Single byte 0x01
  • TCP_PORT: 2-byte port number for dedicated administrator connection

Response Data Format

Response data is a semicolon-delimited string with key-value pairs. Multiple instances are separated by double semicolons ;;.

Basic instance information:

ServerName;<name>;InstanceName;<name>;IsClustered;<Yes|No>;Version;<version>;

Protocol information (one or more may be present):

  • TCP: tcp;<port>;
  • Named Pipes: np;<pipe_path>;
  • VIA: via;<netbios>,<nic>:<port>,...;
  • RPC: rpc;<computer_name>;
  • SPX: spx;<service_name>;
  • ADSP: adsp;<object_name>;
  • Banyan VINES: bv;<item>;<group>;<item>;<group>;<org>;

Field specifications:

  • ServerName: Machine name, maximum 255 bytes
  • InstanceName: Instance name, maximum 255 bytes (recommended ?16 characters)
  • IsClustered: Literal string "Yes" or "No"
  • Version: Dotted decimal format (e.g., "15.0.2000.5"), maximum 16 bytes, must contain only digits and periods
  • TCP port: Decimal port number as string
  • Pipe name: Named pipe path (e.g., \\SERVERNAME\pipe\MSSQL$INSTANCE\sql\query)

Each instance entry is terminated with double semicolons ;;. Protocol information tokens may appear in any order but must not be duplicated within a single instance entry.

Protocol Behavior

Client Behavior

  1. Request transmission:

    • Create UDP socket bound to ephemeral port
    • Send appropriate request message to server port 1434
    • Start timeout timer
  2. Timeout values (recommended):

    • CLNT_UCAST_INST and CLNT_UCAST_DAC: 1 second
    • CLNT_UCAST_EX and CLNT_BCAST_EX: Implementation-defined (typically 0.5-5 seconds)
  3. Response handling:

    • For unicast instance/DAC requests: Accept first valid response or timeout
    • For broadcast/multicast requests: Collect all responses until timeout
    • Ignore invalid or malformed responses
    • Validate response format before passing to higher layer
  4. Response validation:

    • Verify SVR_RESP byte is 0x05
    • Check RESP_SIZE matches actual payload length
    • Parse RESP_DATA according to request type
    • Reject responses where protocol parameter lengths exceed 255 bytes

Server Behavior

  1. Listening: Bind UDP socket to port 1434 and listen for incoming requests

  2. Request processing:

    • CLNT_BCAST_EX / CLNT_UCAST_EX: Return information for all available instances
    • CLNT_UCAST_INST: Return information for specified instance only
    • CLNT_UCAST_DAC: Return DAC port for specified instance
    • Invalid/unknown requests: Silently ignore (no response)
  3. Response construction:

    • Include protocol information only if valid and within size limits
    • If adding protocol info would exceed 1,024 bytes per instance, omit it
    • Prefer including complete protocol information over partial data
    • Return IPv4 endpoints for IPv4 requests, IPv6 endpoints for IPv6 requests
  4. Error handling:

    • If requested instance doesn't exist: No response
    • If no valid endpoint information available: No response
    • Invalid request format: Silently ignore

Example Response Data

Single instance with TCP:

ServerName;MYSERVER;InstanceName;SQLEXPRESS;IsClustered;No;Version;15.0.2000.5;tcp;1433;;

Multiple instances:

ServerName;SRV1;InstanceName;INST1;IsClustered;No;Version;15.0.2000.5;tcp;1433;;ServerName;SRV1;InstanceName;INST2;IsClustered;No;Version;16.0.1000.6;tcp;1434;np;\\SRV1\pipe\MSSQL$INST2\sql\query;;

Parsing Algorithm

  1. Split response data by double semicolons ;; to separate instances
  2. For each instance, split by single semicolon ; to get tokens
  3. Process tokens in pairs as key-value
  4. Recognize protocol keywords: tcp, np, via, rpc, spx, adsp, bv
  5. Build map of instance attributes and available connection protocols

Protocol Characteristics

  • Stateless: Single request-response exchange, no connection state
  • Idempotent: Requests can be repeated without side effects
  • Best-effort: UDP provides no delivery guarantees
  • Unsecured: No authentication or encryption; suitable for trusted networks only
  • Non-deterministic: Broadcast/multicast responses may vary based on network conditions and timing
#!/usr/bin/env python3
import socket
import struct
import sys
SSRP_PORT = 1434
CLNT_BCAST_EX = 0x02
CLNT_UCAST_EX = 0x03
CLNT_UCAST_INST = 0x04
CLNT_UCAST_DAC = 0x0F
SVR_RESP = 0x05
TIMEOUT = 1.0
def parse_instance_data(data):
instances = []
current_pos = 0
while current_pos < len(data):
remaining = data[current_pos:]
if not remaining or remaining == ';;':
break
end_marker = remaining.find(';;')
if end_marker == -1:
instance_data = remaining
else:
instance_data = remaining[:end_marker]
if instance_data:
tokens = instance_data.split(';')
instance = {}
i = 0
while i < len(tokens) - 1:
key = tokens[i]
value = tokens[i + 1]
if key and value:
instance[key] = value
i += 2
if instance:
instances.append(instance)
if end_marker == -1:
break
current_pos += end_marker + 2
return instances
def send_clnt_ucast_ex(host):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(TIMEOUT)
request = struct.pack('B', CLNT_UCAST_EX)
sock.sendto(request, (host, SSRP_PORT))
try:
response, addr = sock.recvfrom(65535)
if response[0] == SVR_RESP:
size = struct.unpack('<H', response[1:3])[0]
data = response[3:3+size].decode('ascii', errors='ignore')
instances = parse_instance_data(data)
return instances
except socket.timeout:
print("No response received")
finally:
sock.close()
return []
def send_clnt_ucast_inst(host, instance_name):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(TIMEOUT)
request = struct.pack('B', CLNT_UCAST_INST) + instance_name.encode('ascii') + b'\x00'
sock.sendto(request, (host, SSRP_PORT))
try:
response, addr = sock.recvfrom(1024)
if response[0] == SVR_RESP:
size = struct.unpack('<H', response[1:3])[0]
data = response[3:3+size].decode('ascii', errors='ignore')
instances = parse_instance_data(data)
return instances
except socket.timeout:
print("No response received")
finally:
sock.close()
return []
def send_clnt_ucast_dac(host, instance_name):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(TIMEOUT)
request = struct.pack('BB', CLNT_UCAST_DAC, 0x01) + instance_name.encode('ascii') + b'\x00'
sock.sendto(request, (host, SSRP_PORT))
try:
response, addr = sock.recvfrom(1024)
if response[0] == SVR_RESP:
size = struct.unpack('<H', response[1:3])[0]
if size == 0x06:
protocol_version = response[3]
dac_port = struct.unpack('<H', response[4:6])[0]
return dac_port
except socket.timeout:
print("No response received")
finally:
sock.close()
return None
def print_instances(instances):
for i, inst in enumerate(instances, 1):
print(f"\nInstance {i}:")
for key, value in inst.items():
print(f" {key}: {value}")
def main():
if len(sys.argv) < 3:
print("Usage:")
print(f" {sys.argv[0]} <host> ex - List all instances")
print(f" {sys.argv[0]} <host> inst <instance_name> - Get specific instance info")
print(f" {sys.argv[0]} <host> dac <instance_name> - Get DAC port")
sys.exit(1)
host = sys.argv[1]
command = sys.argv[2].lower()
if command == 'ex':
print(f"Querying all instances on {host}...")
instances = send_clnt_ucast_ex(host)
if instances:
print(f"\nFound {len(instances)} instance(s):")
print_instances(instances)
else:
print("No instances found")
elif command == 'inst':
if len(sys.argv) < 4:
print("Error: instance name required")
sys.exit(1)
instance_name = sys.argv[3]
print(f"Querying instance '{instance_name}' on {host}...")
instances = send_clnt_ucast_inst(host, instance_name)
if instances:
print_instances(instances)
else:
print("Instance not found")
elif command == 'dac':
if len(sys.argv) < 4:
print("Error: instance name required")
sys.exit(1)
instance_name = sys.argv[3]
print(f"Querying DAC port for instance '{instance_name}' on {host}...")
dac_port = send_clnt_ucast_dac(host, instance_name)
if dac_port:
print(f"\nDAC Port: {dac_port}")
else:
print("DAC port not found")
else:
print(f"Error: Unknown command '{command}'")
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
import socket
import struct
SSRP_PORT = 1434
CLNT_BCAST_EX = 0x02
CLNT_UCAST_EX = 0x03
CLNT_UCAST_INST = 0x04
CLNT_UCAST_DAC = 0x0F
SVR_RESP = 0x05
INSTANCES = [
{
'ServerName': 'TESTSERVER',
'InstanceName': 'MSSQLSERVER',
'IsClustered': 'No',
'Version': '15.0.2000.5',
'tcp': '1433'
},
{
'ServerName': 'TESTSERVER',
'InstanceName': 'SQLEXPRESS',
'IsClustered': 'No',
'Version': '15.0.2000.5',
'tcp': '1434',
'np': r'\\TESTSERVER\pipe\MSSQL$SQLEXPRESS\sql\query'
}
]
DAC_PORTS = {
'MSSQLSERVER': 1434,
'SQLEXPRESS': 1435
}
def build_instance_response(instance):
response = f"ServerName;{instance['ServerName']};InstanceName;{instance['InstanceName']};"
response += f"IsClustered;{instance['IsClustered']};Version;{instance['Version']}"
if 'tcp' in instance:
response += f";tcp;{instance['tcp']}"
if 'np' in instance:
response += f";np;{instance['np']}"
response += ";;"
return response
def handle_clnt_ucast_ex():
responses = []
for instance in INSTANCES:
responses.append(build_instance_response(instance))
data = ''.join(responses).encode('ascii')
size = len(data)
return struct.pack('<BH', SVR_RESP, size) + data
def handle_clnt_ucast_inst(instance_name):
for instance in INSTANCES:
if instance['InstanceName'].upper() == instance_name.upper():
data = build_instance_response(instance).encode('ascii')
size = len(data)
return struct.pack('<BH', SVR_RESP, size) + data
return None
def handle_clnt_ucast_dac(instance_name):
if instance_name.upper() in [k.upper() for k in DAC_PORTS.keys()]:
for key, port in DAC_PORTS.items():
if key.upper() == instance_name.upper():
return struct.pack('<BHB H', SVR_RESP, 0x06, 0x01, port)
return None
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', SSRP_PORT))
print(f"SSRP Server listening on UDP port {SSRP_PORT}")
print(f"Serving {len(INSTANCES)} instance(s)")
while True:
try:
data, addr = sock.recvfrom(1024)
if not data:
continue
print(f"\nRequest from {addr[0]}:{addr[1]}")
print(f" Data: {data.hex()}")
request_type = data[0]
response = None
if request_type == CLNT_BCAST_EX:
print(" Type: CLNT_BCAST_EX")
response = handle_clnt_ucast_ex()
elif request_type == CLNT_UCAST_EX:
print(" Type: CLNT_UCAST_EX")
response = handle_clnt_ucast_ex()
elif request_type == CLNT_UCAST_INST:
instance_name = data[1:].rstrip(b'\x00').decode('ascii')
print(f" Type: CLNT_UCAST_INST (Instance: {instance_name})")
response = handle_clnt_ucast_inst(instance_name)
elif request_type == CLNT_UCAST_DAC:
if len(data) >= 2:
instance_name = data[2:].rstrip(b'\x00').decode('ascii')
print(f" Type: CLNT_UCAST_DAC (Instance: {instance_name})")
response = handle_clnt_ucast_dac(instance_name)
else:
print(f" Type: Unknown (0x{request_type:02X})")
if response:
sock.sendto(response, addr)
print(f" Response sent ({len(response)} bytes)")
else:
print(" No response (instance not found or invalid request)")
except KeyboardInterrupt:
print("\nShutting down...")
break
except Exception as e:
print(f"Error: {e}")
sock.close()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment