Difference between revisions of "Query"

From wiki.vg
Jump to: navigation, search
(Correct endianness.)
 
(23 intermediate revisions by 13 users not shown)
Line 1: Line 1:
Query is a UDP protocol introduced in 1.9pre4 for the purpose of querying server properties. It looks a lot like the UT3 (or GameSpot) Query Protocol [http://wiki.unrealadmin.org/UT3_query_protocol]
+
Query is a UDP protocol introduced in beta 1.9pre4 for querying server properties. It's meant to be compatible with the [http://wiki.unrealadmin.org/UT3_query_protocol UT3 (or GameSpot) Query Protocol]
 +
 
 +
A slightly simpler alternative to the query protocol involves connecting to the main Minecraft TCP port and sending a [[Protocol#Server_List_Ping_.280xFE.29|Server List Ping]] packet, which returns MOTD, number of users and number of slots. More details on the [[Protocol#Server_List_Ping_.280xFE.29|main protocol page]].
  
 
All data types are big-endian, with the exception of <code>short</code>
 
All data types are big-endian, with the exception of <code>short</code>
Line 5: Line 7:
 
== Server Config ==
 
== Server Config ==
  
    enable-query=true
+
enable-query=true
    #query.port=25565
+
query.port=<1-65535>
 +
 
 +
The default port is is the one established in <code>server-port</code>
 +
 
 +
== Base packet format ==
  
== Client to Server Packet Format ==
+
=== Client to Server Packet Format ===
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 17: Line 23:
 
|-  
 
|-  
 
| Magic
 
| Magic
| byte, byte
+
| short
| Always <code>0xFE 0xFD</code>
+
| Always 65277 (0xFEFD)
 
|-
 
|-
 
| Type
 
| Type
 
| byte
 
| byte
| <code>9</code> to handshake, <code>0</code> to stat
+
| <code>9</code> for handshake, <code>0</code> for stat
 
|-
 
|-
 
| Session ID
 
| Session ID
Line 33: Line 39:
 
|}
 
|}
  
== Server to Client Packet Format ==
+
=== Server to Client Packet Format ===
  
 
{| class="wikitable"
 
{| class="wikitable"
Line 54: Line 60:
 
|}
 
|}
  
== Operations ==
+
== Handshake ==
  
=== Handshake ===
+
=== Generating a Session ID ===
 +
The session ID is used identify your requests. The following examples use session ID = 1 (encoded as <code>00 00 00 01</code> on the hex dumps).
  
First, generate a session ID. It can be whatever you want.
+
Only the lower 4-bits on each byte of the session ID should be used as Minecraft does not process the higher 4-bits on each byte. To convert any 4-byte session ID to a valid Minecraft session ID, simply mask the bits with <code>sessionId & 0x0F0F0F0F</code>.
  
==== Request ====
+
=== Request ===
  
 
Send a request with an empty payload.
 
Send a request with an empty payload.
  
<code>
+
{| class="wikitable"
  FE FD 09 00 00 00 01
+
|-
</code>
+
! Field name
 +
! Field Type
 +
! Example
 +
|-
 +
| Magic
 +
| short
 +
| <code>FE FD</code>
 +
|-
 +
| Type
 +
| byte
 +
| <code>09</code>
 +
|-
 +
| Session ID
 +
| int32
 +
| <code>00 00 00 01</code>
 +
|-
 +
| Payload
 +
|
 +
| ''(empty)''
 +
|}
 +
 
 +
Dump:
 +
 
 +
<pre>FE FD 09 00 00 00 01</pre>
 
      
 
      
==== Response ====
+
=== Response ===
  
The response payload will be a session token (note: different to a session id) encoded as a null-terminated string. You should convert it to an int32 and store it.
+
The response payload will be a challenge token encoded as a null-terminated string. You should convert it to an int32 and store it.
  
<code>
+
{| class="wikitable"
  09 00 00 00 01 39 35 31 33 33 30 37 00 | .....9513307.       
+
|-
</code>
+
! Field name
 +
! Field Type
 +
! Example
 +
|-
 +
| Type
 +
| byte
 +
| <code>09</code>
 +
|-
 +
| Session ID
 +
| int32
 +
| <code>00 00 00 01</code>
 +
|-
 +
| Challenge token string
 +
| Null-terminated string
 +
| <code>"9513307\0"</code>
 +
|}
  
=== Basic stat ===
+
In this example, after parsing the string "9513307\0" as an integer and packing it as a big-endian int32, the result should be 00 91 29 5B
  
==== Request ====
+
Dump:
  
Your payload should be your session token, packed as an int32.  
+
<pre>09 00 00 00 01 39 35 31 33 33 30 37 00 | .....9513307.        </pre>
  
<code>
+
== Expiration of the challenge token ==
  FE FD 00 00 00 00 01 00 3A 0D 5F
 
</code>
 
  
==== Response ====
+
Quoting [http://dinnerbone.com/blog/2011/10/14/minecraft-19-has-rcon-and-query/ Dinnerbone's original post] on the Minecraft query protocol:
  
Five null-terminated strings make up the beginning of the payload. They correspond to (motd, gametype, map, numplayers, maxplayers)
+
<blockquote>
 +
Note that the challenge token is bound '''to your IP and port''' (as opposed to the [session ID]), and lasts up to 30 seconds. You read that right, it's up to; it's not "your token will expire after 30 seconds", it's "every token ever" is expired every 30 seconds. This means it's entirely possible that you may get a token and use it within the same second and have it expire.
 +
</blockquote>
  
After these strings, a 2-byte, ''big-endian'' short, giving the hostport
+
Additionally:
  
At the end is a final null-terminated string, giving the hostname
+
<blockquote>
 +
You'll need to provide your challenge token, or you will not receive any reply. If you provide a token and it's wrong, you still won't receive a reply. With that in mind, if you're going to store your challenge token and use it later, then you may want to do some timeout on waiting for a reply, in case the server restarted and your token is no longer valid. It's impossible to identify between an offline server and a server that refused your challenge without any additional requests, so you'll want to try for another challenge token and if that fails then flag them as unavailable
 +
</blockquote>
  
<code>
+
== Basic stat ==
  00 00 00 00 01 41 20 4D 69 6E 65 63 72 61 66 74 | .....A Minecraft
 
  20 53 65 72 76 65 72 00 53 4D 50 00 77 6F 72 6C |  Server.SMP.worl
 
  64 00 32 00 32 30 00 DD 63 31 32 37 2E 30 2E 30 | d.2.20.##127.0.0
 
  2E 31 00                                        | .1.
 
</code>
 
  
=== Full stat ===  
+
=== Request ===
  
==== Request ====
+
Your payload should be your challenge token, packed as an int32.
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Field name
 +
! Field Type
 +
! Example
 +
|-
 +
| Magic
 +
| short
 +
| <code>FE FD</code>
 +
|-
 +
| Type
 +
| byte
 +
| <code>00</code>
 +
|-
 +
| Session ID
 +
| int32
 +
| <code>00 00 00 01</code>
 +
|-
 +
| Challenge token
 +
| int32
 +
| <code>00 91 29 5B</code>
 +
|}
 +
 
 +
Dump:
 +
 
 +
<pre>FE FD 00 00 00 00 01 00 91 29 5B</pre>
 +
 
 +
=== Response ===
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Field name
 +
! Field Type
 +
! Example
 +
|-
 +
| Type
 +
| byte
 +
| <code>00</code>
 +
|-
 +
| Session ID
 +
| int32
 +
| <code>00 00 00 01</code>
 +
|-
 +
| MOTD
 +
| Null-terminated string
 +
| <code>"A Minecraft Server\0"</code>
 +
|-
 +
| gametype
 +
| Null-terminated string
 +
| <code>"SMP\0"</code>
 +
|-
 +
| map
 +
| Null-terminated string
 +
| <code>"world\0"</code>
 +
|-
 +
| numplayers
 +
| Null-terminated string
 +
| <code>"2\0"</code>
 +
|-
 +
| maxplayers
 +
| Null-terminated string
 +
| <code>"20\0"</code>
 +
|-
 +
| hostport
 +
| Little endian short
 +
| <code>DD 63</code> ( = 25565)
 +
|-
 +
| hostip
 +
| Null-terminated string
 +
| <code>"127.0.0.1\0"</code>
 +
|}
 +
 
 +
Dump:
 +
 
 +
<pre>
 +
00 00 00 00 01 41 20 4D 69 6E 65 63 72 61 66 74 | .....A Minecraft
 +
20 53 65 72 76 65 72 00 53 4D 50 00 77 6F 72 6C |  Server.SMP.worl
 +
64 00 32 00 32 30 00 DD 63 31 32 37 2E 30 2E 30 | d.2.20.##127.0.0
 +
2E 31 00                                        | .1.
 +
</pre>
 +
 
 +
== Full stat ==
 +
 
 +
This method is cached every 5 seconds.
 +
 
 +
=== Request ===
  
 
The request is the same as in a basic stat, except the payload '''must''' be padded to 8 bytes. Sending 0x00 0x00 0x00 0x00 at the end works.
 
The request is the same as in a basic stat, except the payload '''must''' be padded to 8 bytes. Sending 0x00 0x00 0x00 0x00 at the end works.
 +
{| class="wikitable"
 +
|-
 +
! Field name
 +
! Field Type
 +
! Example
 +
|-
 +
| Magic
 +
| short
 +
| <code>FE FD</code>
 +
|-
 +
| Type
 +
| byte
 +
| <code>00</code>
 +
|-
 +
| Session ID
 +
| int32
 +
| <code>00 00 00 01</code>
 +
|-
 +
| Challenge token
 +
| int32
 +
| <code>00 91 29 5B</code>
 +
|-
 +
| Padding
 +
|
 +
| <code>00 00 00 00</code>
 +
|}
  
<code>
+
Dump:
  FE FD 00 00 00 00 01 00 3A 0D 5F 00 00 00 00
 
</code>
 
  
==== Response ====
+
<pre>FE FD 00 00 00 00 01 00 91 29 5B 00 00 00 00</pre>
  
You should ignore the first 11 bytes of the payload - they're garbage
+
=== Response ===
  
The response is in two parts. You should split the response around the token <code>\x00\x01player_\x00\x00</code>
+
The response is in two parts. The first part is a list of null-terminated strings, representing (key1, value1, key2, value2 ...). The second part is another list of null-terminated strings, each representing a player.
  
The first part is a list of null-terminated strings, representing (key1, value1, key2, value2 ...)
+
A simple way to parse the payload is to ignore the first 11 bytes, and then split the response around the token <code>\x00\x01player_\x00\x00</code>. At the very end, there's an extra null byte.
  
The second part is another list of null-terminated strings, each representing a player.
+
{| class="wikitable"
 +
|-
 +
! Field name
 +
! Field Type
 +
! Example
 +
! Notes
 +
|-
 +
| Type
 +
| byte
 +
| <code>00</code>
 +
|
 +
|-
 +
| Session ID
 +
| int32
 +
| <code>00 00 00 01</code>
 +
|
 +
|-
 +
| Padding
 +
| 11 bytes, constant
 +
| <code>73 70 6C 69 74 6E 75 6D 00 80 00</code>
 +
| Meaningless
 +
|-
 +
| K, V section
 +
| Several pairs of null-terminated strings
 +
| <code>hostname\0A Minecraft Server\0gametype\0SMP\0 ... hostip\0127.0.0.1\0\0</code>
 +
| Terminated when you read a 0-length key (break from loop, don't read value)
 +
|-
 +
| Padding
 +
| 10 bytes, constant
 +
| <code>01 70 6C 61 79 65 72 5F 00 00</code>
 +
| Meaningless
 +
|-
 +
| Players section
 +
| Several null-terminated strings
 +
| <code>"barneygale\0TkTech\0 ... sadimusi\0\0"</code>
 +
| Terminated when you read a 0-length player name
 +
|}
  
At the very end there's an extra null byte.
+
Dump:
  
<code>
+
<pre>
  00 00 00 00 01 73 70 6C 69 74 6E 75 6D 00 80 00 | .....splitnum...
+
00 00 00 00 01 73 70 6C 69 74 6E 75 6D 00 80 00 | .....splitnum...
  68 6F 73 74 6E 61 6D 65 00 41 20 4D 69 6E 65 63 | hostname.A minec
+
68 6F 73 74 6E 61 6D 65 00 41 20 4D 69 6E 65 63 | hostname.A minec
  72 61 66 74 20 53 65 72 76 65 72 00 67 61 6D 65 | raft Server.game
+
72 61 66 74 20 53 65 72 76 65 72 00 67 61 6D 65 | raft Server.game
  74 79 70 65 00 53 4D 50 00 67 61 6D 65 5F 69 64 | type.SMP.game_id
+
74 79 70 65 00 53 4D 50 00 67 61 6D 65 5F 69 64 | type.SMP.game_id
  00 4D 49 4E 45 43 52 41 46 54 00 76 65 72 73 69 | .MINECRAFT.versi
+
00 4D 49 4E 45 43 52 41 46 54 00 76 65 72 73 69 | .MINECRAFT.versi
  6F 6E 00 42 65 74 61 20 31 2E 39 20 50 72 65 72 | on.Beta 1.9 Prer
+
6F 6E 00 42 65 74 61 20 31 2E 39 20 50 72 65 72 | on.Beta 1.9 Prer
  65 6C 65 61 73 65 20 34 00 70 6C 75 67 69 6E 73 | elease 4.plugins
+
65 6C 65 61 73 65 20 34 00 70 6C 75 67 69 6E 73 | elease 4.plugins
  00 00 6D 61 70 00 77 6F 72 6C 64 00 6E 75 6D 70 | ..map.world.nump
+
00 00 6D 61 70 00 77 6F 72 6C 64 00 6E 75 6D 70 | ..map.world.nump
  6C 61 79 65 72 73 00 32 00 6D 61 78 70 6C 61 79 | layers.2.maxplay
+
6C 61 79 65 72 73 00 32 00 6D 61 78 70 6C 61 79 | layers.2.maxplay
  65 72 73 00 32 30 00 68 6F 73 74 70 6F 72 74 00 | ers.20.hostport.
+
65 72 73 00 32 30 00 68 6F 73 74 70 6F 72 74 00 | ers.20.hostport.
  32 35 35 36 35 00 68 6F 73 74 6E 61 6D 65 00 31 | 25565.hostname.1
+
32 35 35 36 35 00 68 6F 73 74 69 70 00 31 32 37 | 25565.hostip.127
  32 37 2E 30 2E 30 2E 31 00 00 01 70 6C 61 79 65 | 27.0.0.1...playe
+
2E 30 2E 30 2E 31 00 00 01 70 6C 61 79 65 72 5F | .0.0.1...player_
  72 5F 00 00 62 61 72 6E 65 79 67 61 6C 65 00 56 | r_..barneygale.V
+
00 00 62 61 72 6E 65 79 67 61 6C 65 00 56 69 76 | ..barneygale.Viv
  69 76 61 6C 61 68 65 6C 76 69 67 00 00         | ivalahelvig..
+
61 6C 61 68 65 6C 76 69 67 00 00               | alahelvig..
</code>
+
</pre>
 +
 
 +
==== K, V section ====
 +
 
 +
{| class="wikitable"
 +
|-
 +
! Key
 +
! Example value
 +
! Description
 +
|-
 +
| hostname
 +
|'A Minecraft Server'
 +
| MOTD for the current server
 +
|-
 +
| game type
 +
| 'SMP'
 +
| hardcoded to SMP
 +
|-
 +
| game_id
 +
| 'MINECRAFT'
 +
| hardcoded to MINECRAFT
 +
|-
 +
| version
 +
| '1.2.5'
 +
| Server version
 +
|-
 +
| plugins
 +
| 'CraftBukkit on Bukkit 1.2.5-R4.0:
 +
WorldEdit 5.3; CommandBook 2.1'
 +
| List of plugins, not used by the vanilla server, where it is an empty string (but still null terminated, see the hex dump above).
 +
This is the format proposed by Dinnerbone and currently used by Bukkit: <br />
 +
<code>[SERVER_MOD_NAME[: PLUGIN_NAME(; PLUGIN_NAME...)]]</code>
 +
|-
 +
| map
 +
| 'world'
 +
| Name of the current map
 +
|-
 +
|numplayers
 +
| '1'
 +
| Number of online players. The string could be parsed to a number.
 +
|-
 +
| max players
 +
| '20'
 +
| Max number of players on the server. The string could be parsed to a number
 +
|-   
 +
| host port
 +
| '25565'
 +
| Server port. The string could be parsed to a number
 +
|-
 +
| host ip
 +
| '127.0.0.1'
 +
| The IP address the server is listening/was contacted on
 +
|}
  
 
== Example implementations ==
 
== Example implementations ==
  
 
* https://github.com/barneygale/MCQuery (python)
 
* https://github.com/barneygale/MCQuery (python)
 +
* https://github.com/kmpm/node-mcquery (node.js)
 +
* https://github.com/Dinnerbone/mcstatus (python)
 +
* https://github.com/A2PLab/minelib (scala)
 +
* https://github.com/ryanshawty/MCJQuery (java)
 +
* https://github.com/Erikvv/Minecraft-Query-cpp (c++)
 +
* https://github.com/winny-/mcstat (php)
 +
* https://github.com/coNQP/mcipc (python 3.6)
 +
 +
[[Category:Minecraft Modern]]

Latest revision as of 22:02, 24 July 2019

Query is a UDP protocol introduced in beta 1.9pre4 for querying server properties. It's meant to be compatible with the UT3 (or GameSpot) Query Protocol

A slightly simpler alternative to the query protocol involves connecting to the main Minecraft TCP port and sending a Server List Ping packet, which returns MOTD, number of users and number of slots. More details on the main protocol page.

All data types are big-endian, with the exception of short

Server Config

enable-query=true
query.port=<1-65535>

The default port is is the one established in server-port

Base packet format

Client to Server Packet Format

Field name Field Type Notes
Magic short Always 65277 (0xFEFD)
Type byte 9 for handshake, 0 for stat
Session ID int32
Payload Varies See below

Server to Client Packet Format

Field name Field Type Notes
Type byte 9 for handshake, 0 for stat
Session ID int32
Payload Varies See below

Handshake

Generating a Session ID

The session ID is used identify your requests. The following examples use session ID = 1 (encoded as 00 00 00 01 on the hex dumps).

Only the lower 4-bits on each byte of the session ID should be used as Minecraft does not process the higher 4-bits on each byte. To convert any 4-byte session ID to a valid Minecraft session ID, simply mask the bits with sessionId & 0x0F0F0F0F.

Request

Send a request with an empty payload.

Field name Field Type Example
Magic short FE FD
Type byte 09
Session ID int32 00 00 00 01
Payload (empty)

Dump:

FE FD 09 00 00 00 01

Response

The response payload will be a challenge token encoded as a null-terminated string. You should convert it to an int32 and store it.

Field name Field Type Example
Type byte 09
Session ID int32 00 00 00 01
Challenge token string Null-terminated string "9513307\0"

In this example, after parsing the string "9513307\0" as an integer and packing it as a big-endian int32, the result should be 00 91 29 5B

Dump:

09 00 00 00 01 39 35 31 33 33 30 37 00 | .....9513307.         

Expiration of the challenge token

Quoting Dinnerbone's original post on the Minecraft query protocol:

Note that the challenge token is bound to your IP and port (as opposed to the [session ID]), and lasts up to 30 seconds. You read that right, it's up to; it's not "your token will expire after 30 seconds", it's "every token ever" is expired every 30 seconds. This means it's entirely possible that you may get a token and use it within the same second and have it expire.

Additionally:

You'll need to provide your challenge token, or you will not receive any reply. If you provide a token and it's wrong, you still won't receive a reply. With that in mind, if you're going to store your challenge token and use it later, then you may want to do some timeout on waiting for a reply, in case the server restarted and your token is no longer valid. It's impossible to identify between an offline server and a server that refused your challenge without any additional requests, so you'll want to try for another challenge token and if that fails then flag them as unavailable

Basic stat

Request

Your payload should be your challenge token, packed as an int32.

Field name Field Type Example
Magic short FE FD
Type byte 00
Session ID int32 00 00 00 01
Challenge token int32 00 91 29 5B

Dump:

FE FD 00 00 00 00 01 00 91 29 5B

Response

Field name Field Type Example
Type byte 00
Session ID int32 00 00 00 01
MOTD Null-terminated string "A Minecraft Server\0"
gametype Null-terminated string "SMP\0"
map Null-terminated string "world\0"
numplayers Null-terminated string "2\0"
maxplayers Null-terminated string "20\0"
hostport Little endian short DD 63 ( = 25565)
hostip Null-terminated string "127.0.0.1\0"

Dump:

00 00 00 00 01 41 20 4D 69 6E 65 63 72 61 66 74 | .....A Minecraft
20 53 65 72 76 65 72 00 53 4D 50 00 77 6F 72 6C |  Server.SMP.worl
64 00 32 00 32 30 00 DD 63 31 32 37 2E 30 2E 30 | d.2.20.##127.0.0
2E 31 00                                        | .1.

Full stat

This method is cached every 5 seconds.

Request

The request is the same as in a basic stat, except the payload must be padded to 8 bytes. Sending 0x00 0x00 0x00 0x00 at the end works.

Field name Field Type Example
Magic short FE FD
Type byte 00
Session ID int32 00 00 00 01
Challenge token int32 00 91 29 5B
Padding 00 00 00 00

Dump:

FE FD 00 00 00 00 01 00 91 29 5B 00 00 00 00

Response

The response is in two parts. The first part is a list of null-terminated strings, representing (key1, value1, key2, value2 ...). The second part is another list of null-terminated strings, each representing a player.

A simple way to parse the payload is to ignore the first 11 bytes, and then split the response around the token \x00\x01player_\x00\x00. At the very end, there's an extra null byte.

Field name Field Type Example Notes
Type byte 00
Session ID int32 00 00 00 01
Padding 11 bytes, constant 73 70 6C 69 74 6E 75 6D 00 80 00 Meaningless
K, V section Several pairs of null-terminated strings hostname\0A Minecraft Server\0gametype\0SMP\0 ... hostip\0127.0.0.1\0\0 Terminated when you read a 0-length key (break from loop, don't read value)
Padding 10 bytes, constant 01 70 6C 61 79 65 72 5F 00 00 Meaningless
Players section Several null-terminated strings "barneygale\0TkTech\0 ... sadimusi\0\0" Terminated when you read a 0-length player name

Dump:

00 00 00 00 01 73 70 6C 69 74 6E 75 6D 00 80 00 | .....splitnum...
68 6F 73 74 6E 61 6D 65 00 41 20 4D 69 6E 65 63 | hostname.A minec
72 61 66 74 20 53 65 72 76 65 72 00 67 61 6D 65 | raft Server.game
74 79 70 65 00 53 4D 50 00 67 61 6D 65 5F 69 64 | type.SMP.game_id
00 4D 49 4E 45 43 52 41 46 54 00 76 65 72 73 69 | .MINECRAFT.versi
6F 6E 00 42 65 74 61 20 31 2E 39 20 50 72 65 72 | on.Beta 1.9 Prer
65 6C 65 61 73 65 20 34 00 70 6C 75 67 69 6E 73 | elease 4.plugins
00 00 6D 61 70 00 77 6F 72 6C 64 00 6E 75 6D 70 | ..map.world.nump
6C 61 79 65 72 73 00 32 00 6D 61 78 70 6C 61 79 | layers.2.maxplay
65 72 73 00 32 30 00 68 6F 73 74 70 6F 72 74 00 | ers.20.hostport.
32 35 35 36 35 00 68 6F 73 74 69 70 00 31 32 37 | 25565.hostip.127
2E 30 2E 30 2E 31 00 00 01 70 6C 61 79 65 72 5F | .0.0.1...player_
00 00 62 61 72 6E 65 79 67 61 6C 65 00 56 69 76 | ..barneygale.Viv
61 6C 61 68 65 6C 76 69 67 00 00                | alahelvig..

K, V section

Key Example value Description
hostname 'A Minecraft Server' MOTD for the current server
game type 'SMP' hardcoded to SMP
game_id 'MINECRAFT' hardcoded to MINECRAFT
version '1.2.5' Server version
plugins 'CraftBukkit on Bukkit 1.2.5-R4.0:

WorldEdit 5.3; CommandBook 2.1'

List of plugins, not used by the vanilla server, where it is an empty string (but still null terminated, see the hex dump above).

This is the format proposed by Dinnerbone and currently used by Bukkit:
[SERVER_MOD_NAME[: PLUGIN_NAME(; PLUGIN_NAME...)]]

map 'world' Name of the current map
numplayers '1' Number of online players. The string could be parsed to a number.
max players '20' Max number of players on the server. The string could be parsed to a number
host port '25565' Server port. The string could be parsed to a number
host ip '127.0.0.1' The IP address the server is listening/was contacted on

Example implementations