Skip to content

Commit 0e830cd

Browse files
committed
Implement SRV DNS record lookup for multiplayer server connections
Replace instances of 25565 with ServerItem.DEFAULT_PORT Add portable static NetworkUtils DNS resolution methods Use NetworkUtils DNS resolution when polling and connecting to servers
1 parent f07c51d commit 0e830cd

File tree

10 files changed

+152
-20
lines changed

10 files changed

+152
-20
lines changed

lib/dnsjava-2.1.3.jar

288 KB
Binary file not shown.

src/minecraft/net/minecraft/src/NetClientHandler.java

+20-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import java.io.BufferedReader;
44
import java.io.IOException;
55
import java.io.InputStreamReader;
6-
import java.net.InetAddress;
6+
// Spout start
7+
import java.net.InetSocketAddress;
8+
// Spout end
79
import java.net.Socket;
810
import java.net.URL;
911
import java.net.UnknownHostException;
@@ -12,10 +14,15 @@
1214
import java.util.List;
1315
import java.util.Map;
1416
import java.util.Random;
15-
//Spout start
17+
18+
// Spout start
19+
import net.minecraft.client.Minecraft;
20+
1621
import org.spoutcraft.client.SpoutClient;
1722
import org.spoutcraft.client.io.FileDownloadThread;
18-
//SPout end
23+
import org.spoutcraft.client.util.NetworkUtils;
24+
// Spout end
25+
1926
import net.minecraft.client.Minecraft;
2027
import net.minecraft.src.Block;
2128
import net.minecraft.src.Chunk;
@@ -138,14 +145,20 @@ public class NetClientHandler extends NetHandler {
138145
public List playerNames = new ArrayList();
139146
public int currentServerMaxPlayers = 20;
140147
Random rand = new Random();
141-
//Spout start
148+
// Spout start
142149
long timeout = System.currentTimeMillis() + 5000;
150+
// Spout end
143151

144152
public NetClientHandler(Minecraft var1, String var2, int var3) throws UnknownHostException, IOException {
145153
this.mc = var1;
146-
Socket var4 = new Socket(InetAddress.getByName(var2), var3);
147-
this.netManager = new NetworkManager(var4, "Client", this);
148-
//Spout start
154+
155+
// Spout start
156+
InetSocketAddress address = NetworkUtils.resolve(var2, var3);
157+
if (address.isUnresolved()) {
158+
throw new UnknownHostException(address.getHostName());
159+
}
160+
this.netManager = new NetworkManager(new Socket(address.getAddress(), address.getPort()), "Client", this);
161+
149162
org.spoutcraft.client.gui.error.GuiConnectionLost.lastServerIp = var2;
150163
org.spoutcraft.client.gui.error.GuiConnectionLost.lastServerPort = var3;
151164
//Spout end

src/minecraft/org/spoutcraft/client/ReconnectManager.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
import java.lang.String;
2929

30+
import org.spoutcraft.client.gui.server.ServerItem;
31+
3032
import net.minecraft.src.GuiConnecting;
3133

3234
public class ReconnectManager {
@@ -59,7 +61,7 @@ public static void detectKick(String infoString, String reason, Object[] objects
5961
}
6062
} else if (split.length == 2) {
6163
hostName = split[1].trim();
62-
portNum = 25565;
64+
portNum = ServerItem.DEFAULT_PORT;
6365
}
6466
}
6567
}

src/minecraft/org/spoutcraft/client/gui/server/FavoritesModel.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void load() {
8888
for (HashMap<String, Object> item: list) {
8989
String title = "";
9090
String ip = "";
91-
int port = 25565;
91+
int port = ServerItem.DEFAULT_PORT;
9292
int databaseId = -1;
9393
if (item.containsKey("title")) title = (String) item.get("title");
9494
if (item.containsKey("ip")) ip = (String) item.get("ip");
@@ -125,14 +125,14 @@ private void importLegacyTXT() {
125125
String title = args[0];
126126
String split[] = args[1].split(":");
127127
String ip = split[0];
128-
int port = split.length > 1 ? Integer.parseInt(split[1]) : 25565;
128+
int port = split.length > 1 ? Integer.parseInt(split[1]) : ServerItem.DEFAULT_PORT;
129129
addServer(title, ip, port);
130130
}
131131
if (args.length == 3) {
132132
String title = args[1];
133133
String split[] = args[0].split(":");
134134
String ip = split[0];
135-
int port = split.length > 1 ? Integer.parseInt(split[1]) : 25565;
135+
int port = split.length > 1 ? Integer.parseInt(split[1]) : ServerItem.DEFAULT_PORT;
136136
int databaseId = Integer.parseInt(args[2]);
137137
addServer(title, ip, port, databaseId);
138138
}

src/minecraft/org/spoutcraft/client/gui/server/GuiAddFavorite.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ public class GuiAddFavorite extends GuiScreen {
5050

5151
public GuiAddFavorite(String server, GuiScreen parent) {
5252
String splt[] = server.split(":");
53-
int port = 25565;
53+
int port = ServerItem.DEFAULT_PORT;
5454
if (splt.length > 0) {
5555
server = splt[0];
5656
if (splt.length > 1) {
5757
try {
5858
port = Integer.valueOf(splt[1]);
5959
} catch (NumberFormatException e) {
60-
port = 25565;
60+
port = ServerItem.DEFAULT_PORT;
6161
}
6262
}
6363
} else {
@@ -105,7 +105,7 @@ public void initGui() {
105105
textIp.setHeight(20);
106106
textIp.setX(left).setY(top);
107107
getScreen().attachWidget(spoutcraft, textIp);
108-
textIp.setText(item.getIp() + (item.getPort()!=25565?":"+item.getPort():""));
108+
textIp.setText(item.getIp() + (item.getPort() != ServerItem.DEFAULT_PORT ? ":" + item.getPort() : ""));
109109
top+=25;
110110

111111
buttonClear = new GenericButton("Clear");
@@ -171,7 +171,7 @@ protected void buttonClicked(Button btn) {
171171
textIp.setText("");
172172
item.setTitle("");
173173
item.setIp("");
174-
item.setPort(25565);
174+
item.setPort(ServerItem.DEFAULT_PORT);
175175
updateButtons();
176176
}
177177
}
@@ -191,7 +191,7 @@ private void updateItem() {
191191
item.setPort(Integer.valueOf(split[1]));
192192
} catch(Exception e) {
193193
// Handles both InvalidNumber and OutOfRange exceptions, yay
194-
item.setPort(25565);
194+
item.setPort(ServerItem.DEFAULT_PORT);
195195
}
196196
}
197197

src/minecraft/org/spoutcraft/client/gui/server/GuiFavorites.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ public void doQuickJoin() {
228228
if (!adress.isEmpty()) {
229229
String split[] = adress.split(":");
230230
String ip = split[0];
231-
int port = split.length > 1 ? Integer.parseInt(split[1]) : 25565;
231+
int port = split.length > 1 ? Integer.parseInt(split[1]) : ServerItem.DEFAULT_PORT;
232232
SpoutClient.getHandle().gameSettings.lastServer = adress.replace(":", "_");
233233
SpoutClient.getHandle().gameSettings.saveOptions();
234234
SpoutClient.getInstance().getServerManager().join(ip, port, this, "Favorites");

src/minecraft/org/spoutcraft/client/gui/server/GuiServerInfo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ protected void createInstances() {
135135
labelAccess.setTextColor(new Color(0xffaaaaaa));
136136
content.attachWidget(spoutcraft, labelAccess);
137137

138-
labelAddress = new GenericLabel(item.getIp() + (item.getPort()!=25565?item.getPort():""));
138+
labelAddress = new GenericLabel(item.getIp() + (item.getPort() != ServerItem.DEFAULT_PORT ? item.getPort() : ""));
139139
labelAddress.setTextColor(new Color(0xffaaaaaa));
140140
content.attachWidget(spoutcraft, labelAddress);
141141

src/minecraft/org/spoutcraft/client/gui/server/PollResult.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
import org.spoutcraft.client.SpoutClient;
4545
import org.spoutcraft.client.io.MirrorUtils;
46+
import org.spoutcraft.client.util.NetworkUtils;
4647
import org.spoutcraft.spoutcraftapi.packet.PacketUtil;
4748

4849
public class PollResult {
@@ -183,7 +184,12 @@ public void run() {
183184
long start = System.currentTimeMillis();
184185
sock = new Socket();
185186
sock.setSoTimeout(10000);
186-
sock.connect(new InetSocketAddress(ip, port), 10000);
187+
InetSocketAddress address = NetworkUtils.resolve(ip, port);
188+
if (address.isUnresolved()) {
189+
ping = PING_UNKNOWN;
190+
return;
191+
}
192+
sock.connect(address, 10000);
187193
sock.setTcpNoDelay(true);
188194
sock.setTrafficClass(18);
189195

src/minecraft/org/spoutcraft/client/gui/server/ServerItem.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
import org.spoutcraft.spoutcraftapi.gui.RenderUtil;
4444

4545
public class ServerItem implements ListWidgetItem {
46+
/**
47+
* The default Minecraft server port.
48+
*/
49+
public static final int DEFAULT_PORT = 25565;
50+
4651
protected ListWidget widget;
4752

4853
protected String ip;
@@ -213,7 +218,7 @@ public void render(int x, int y, int width, int height) {
213218
}
214219
SpoutClient.getHandle().renderEngine.bindTexture(SpoutClient.getHandle().renderEngine.getTexture("/gui/icons.png"));
215220
RenderUtil.drawTexturedModalRectangle(x + width - 2 - 10, y + 2, 0 + xOffset * 10, 176 + yOffset * 8, 10, 8, 0f);
216-
if (port != 25565) {
221+
if (port != DEFAULT_PORT) {
217222
font.drawStringWithShadow(ip + ":" +port, x+marginleft, y+20, 0xaaaaaa);
218223
} else {
219224
font.drawStringWithShadow(ip, x+marginleft, y+20, 0xaaaaaa);

src/minecraft/org/spoutcraft/client/util/NetworkUtils.java

+106
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,24 @@
3030
import java.io.IOException;
3131
import java.io.InputStreamReader;
3232
import java.net.HttpURLConnection;
33+
import java.net.InetSocketAddress;
3334
import java.net.URISyntaxException;
3435
import java.net.URL;
36+
import java.util.regex.Pattern;
37+
38+
import org.spoutcraft.client.gui.server.ServerItem;
39+
import org.xbill.DNS.Lookup;
40+
import org.xbill.DNS.Record;
41+
import org.xbill.DNS.SRVRecord;
42+
import org.xbill.DNS.TextParseException;
43+
import org.xbill.DNS.Type;
3544

3645
public class NetworkUtils {
46+
/**
47+
* A {@link Pattern} matching valid DNS hostnames (as opposed to IP addresses).
48+
*/
49+
private static final Pattern HOSTNAME_PATTERN = Pattern.compile("[a-zA-Z-]");
50+
3751
public static void pingUrl(String Url) {
3852
try {
3953
URL url = new URL(Url);
@@ -58,4 +72,96 @@ public static void openInBrowser(String url) {
5872
e.printStackTrace();
5973
}
6074
}
75+
76+
/**
77+
* Resolves a hostname to an {@link InetSocketAddress}, encapsulating an IP address and
78+
* port. <code>hostname</code> may represent a valid DNS hostname or an IP address. If
79+
* <code>hostname</code> is a valid DNS hostname <em>and</em> <code>port</code> is equal
80+
* to {@link ServerItem#DEFAULT_PORT} (implying the user may not have specified a server
81+
* port), a <a href="http://en.wikipedia.org/wiki/SRV_record">SRV record</a> lookup may
82+
* be attempted&mdash;see {@link #resolve(String)} for implementation details.
83+
* <p>
84+
* This method is the equivalent of passing <code>true</code> to {@link #resolve(String, int, boolean)}.
85+
*
86+
* @param hostname the DNS hostname to resolve.
87+
* @param port the port number to encapsulate within the <code>InetSocketAddress</code>.
88+
* @return an {@link InetSocketAddress}, which may be marked unresolved if hostname lookup failed.
89+
*/
90+
public static InetSocketAddress resolve(String hostname, int port) {
91+
return resolve(hostname, port, true);
92+
}
93+
94+
/**
95+
* Resolves a hostname to an {@link InetSocketAddress}, encapsulating an IP address and
96+
* port. An optional <a href="http://en.wikipedia.org/wiki/SRV_record">SRV record</a>
97+
* lookup may be attempted&mdash;see {@link #resolve(String)} for implementation details.
98+
* A SRV record lookup will only be attempted if <code>hostname</code> represents a valid
99+
* DNS hostname, and <code>port</code> is equal to {@link ServerItem#DEFAULT_PORT} (implying
100+
* the user may not have specified a server port).
101+
* <p>
102+
* If <code>srv</code> is <code>true</code> and SRV record lookup fails or returns no
103+
* results, a regular DNS lookup will be attempted.
104+
* <p>
105+
* If the given <code>hostname</code> represents an IP address, it will not be resolved.
106+
*
107+
* @param hostname the DNS hostname to resolve.
108+
* @param port the port number to encapsulate within the <code>InetSocketAddress</code>.
109+
* @param srv whether to attempt a SRV record lookup.
110+
* @return an {@link InetSocketAddress}, which may be marked unresolved if hostname lookup failed.
111+
*/
112+
public static InetSocketAddress resolve(String hostname, int port, boolean srv) {
113+
if (srv && (port == ServerItem.DEFAULT_PORT) && (HOSTNAME_PATTERN.matcher(hostname).find())) {
114+
try {
115+
InetSocketAddress address = resolve(hostname);
116+
if (address != null) {
117+
return address;
118+
}
119+
} catch (TextParseException e) {
120+
// Do nothing: fall back on a regular DNS lookup before failing
121+
}
122+
}
123+
124+
return new InetSocketAddress(hostname, port);
125+
}
126+
127+
/**
128+
* Resolves a hostname to an {@link InetSocketAddress}, encapsulating an IP address and
129+
* port, using a <a href="http://en.wikipedia.org/wiki/SRV_record">SRV record</a> lookup.
130+
* This method assumes <code>minecraft</code> as the service name during the DNS query,
131+
* such that a matching record looks like:
132+
* <p>
133+
* <code>_minecraft._tcp.example.com. 86400 IN SRV 0 1 25565 minecraft.example.com.</code>
134+
* <p>
135+
* If multiple SRV records exist for a given hostname, the record with the highest priority
136+
* (that is, the lowest priority value) is selected. This implementation does not take
137+
* into account relative weights for records with identical priorities, and behavior in
138+
* such cases is undefined.
139+
*
140+
* @param hostname the DNS hostname to on which to perform a SRV lookup.
141+
* @return an {@link InetSocketAddress}, or <code>null</code> if no SRV record was found.
142+
* @throws TextParseException if the hostname is malformed.
143+
*/
144+
public static InetSocketAddress resolve(String hostname) throws TextParseException {
145+
String query = "_minecraft._tcp." + hostname.trim();
146+
Record[] records = new Lookup(query, Type.SRV).run();
147+
148+
if ((records != null) && (records.length > 0)) {
149+
SRVRecord srv = (SRVRecord)records[0];
150+
151+
if (records.length > 1) {
152+
for (int i = 1; i < records.length; i++) {
153+
SRVRecord record = (SRVRecord)records[i];
154+
if (record.getPriority() < srv.getPriority()) {
155+
srv = record;
156+
}
157+
}
158+
}
159+
160+
String host = srv.getTarget().toString().replaceAll("\\.+$", "");
161+
int port = srv.getPort();
162+
return new InetSocketAddress(host, port);
163+
}
164+
165+
return null;
166+
}
61167
}

0 commit comments

Comments
 (0)