This commit is contained in:
Anthony Mineo 2021-10-06 17:55:39 -04:00
commit 3b22db761b
12 changed files with 592 additions and 0 deletions

4
.formatter.exs Normal file
View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

29
.gitignore vendored Normal file
View file

@ -0,0 +1,29 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
t2_server_query-*.tar
# Temporary files, for example, from tests.
/tmp/
# Ignore VSCode Extentions Files
.elixir_ls

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# T2ServerQuery
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `t2_server_query` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:t2_server_query, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/t2_server_query](https://hexdocs.pm/t2_server_query).

16
lib/t2_server_query.ex Normal file
View file

@ -0,0 +1,16 @@
defmodule T2ServerQuery do
@moduledoc """
Documentation for `T2ServerQuery`.
"""
require Logger
# Just a simple debug logging util
def log(thing_to_log) do
Logger.info(inspect thing_to_log)
IO.puts "\n____________________________________________\n"
thing_to_log
end
end

View file

@ -0,0 +1,185 @@
defmodule T2ServerQuery.PacketParser do
@moduledoc """
Documentation for `T2ServerQuery.PacketParser`.
"""
alias T2ServerQuery.QueryResult
def init({:error, host}, _) do
results = %QueryResult{}
{:error,
%{results |
server_status: :offline,
server_name: host,
server_description: "Host unreachable, timed out."
}
}
end
def init(info_packet, status_packet) when is_binary(info_packet) and is_binary(status_packet) do
info_results = info_packet
|> decode_clean_packet()
|> handle_info_packet()
status_results = status_packet
|> decode_clean_packet()
|> handle_status_packet()
|> parse_player_team_scores()
pack_results({:ok, status_results, info_results})
end
defp pack_results({:ok, status_results, info_results}) do
results = %QueryResult{}
{:ok,
%{results |
server_status: :online,
server_name: info_results.server_name,
game_type: status_results.game_type,
mission_type: status_results.mission_type,
map_name: status_results.map_name,
player_count: status_results.player_count,
max_player_count: status_results.max_player_count,
bot_count: status_results.bot_count,
server_description: status_results.server_description,
team_count: status_results.team_count,
teams: status_results.teams,
players: status_results.players
}
}
end
# Info packet structure
defp handle_info_packet({:ok, info_packet}) do
<<
_header :: size(192),
server_name :: bitstring
>> = info_packet
%{server_name: server_name}
end
# Status packet structure
defp handle_status_packet({:ok, status_packet}) do
#IO.inspect status_packet, limit: :infinity
<<
_header :: size(48),
game_type_length :: little-integer,
game_type :: binary-size(game_type_length),
mission_type_length :: little-integer,
mission_type :: binary-size(mission_type_length),
map_name_length :: little-integer,
map_name :: binary-size(map_name_length),
_skip_a :: size(8),
player_count :: little-integer,
max_player_count :: little-integer,
bot_count :: little-integer,
_skip_b :: size(16),
server_description_length :: little-integer,
server_description :: binary-size(server_description_length),
_skip_c :: size(16),
team_count :: binary-size(1),
rest :: bitstring
>> = status_packet
%{
game_type_length: game_type_length,
game_type: game_type,
mission_type_length: mission_type_length,
mission_type: mission_type,
map_name_length: map_name_length,
map_name: map_name,
player_count: player_count,
max_player_count: max_player_count,
bot_count: bot_count,
server_description_length: server_description_length,
server_description: server_description,
team_count: String.to_integer(team_count),
teams: [],
players: [],
data: rest
}
end
# Take the ..rest of the status packet and parse out the team and player scores
defp parse_player_team_scores(packet) do
## Break the status query packet into multiple parts
## raw_game_info contains the map, gametype, mod, and description
## raw_players_info contains players, assigned team and score
[raw_team_scores | raw_players_info] = String.split(packet.data, "\n#{packet.player_count}", trim: true)
pack_teams = raw_team_scores
|> String.trim_leading
|> String.split("\n")
|> Enum.map(&parse_team_scores(&1))
|> Enum.to_list
pack_players = raw_players_info
|> clean_player_info()
|> Enum.map(&parse_player_scores(&1))
|> Enum.to_list
# We're done parsing the data key so we can remove it from our compiled struct
cleaned_packet = Map.delete(packet, :data)
%{cleaned_packet | teams: pack_teams, players: pack_players }
end
# Convert player array into a map
# parse_player_scores(["Inferno", "305"])
# > %{team: "Inferno", score: 305}
defp parse_team_scores(raw_team_scores) do
Enum.zip([:name, :score], String.split(raw_team_scores, "\t"))
|> Map.new
|> convert_score()
end
# Convert player array into a map
# parse_player_scores(["Anthony", "Storm", "100"])
# > %{player: "ElKapitan ", score: 100, team: "Inferno"}
defp parse_player_scores(player) do
Enum.zip([:player, :team, :score], String.split(player, "\t", trim: true))
|> Map.new
|> convert_score()
end
# Clean and spaces that might be in the packet for odd reason
defp decode_clean_packet(packet) do
packet
|> String.replace(" ", "")
|> Base.decode16(case: :mixed)
end
# Convert string scores into integers
defp convert_score(%{score: score} = data) when is_binary(score) and not is_nil(data), do: %{data | score: String.to_integer(score)}
defp convert_score(%{score: score} = data) when is_integer(score) and not is_nil(data), do: data
defp convert_score(data), do: data
# Strip all non-printable UTF chars but preserve spaces, tabs and new-lines
defp clean_player_info(raw_players_info) do
Regex.replace(~r/(*UTF)[^\w\ \t\n\/*+-]+/, List.to_string(raw_players_info), "")
|> String.trim_leading
|> String.split("\n")
end
end

View file

@ -0,0 +1,65 @@
defmodule T2ServerQuery.QueryResult do
@moduledoc """
Shape of the server query result struct.
%T2ServerQuery.QueryResult{
server_status: :online,
bot_count: 30,
game_type: "Classic",
map_name: "Cold as Ice [b]",
max_player_count: 64,
mission_type: "Capture the Flag",
player_count: 29,
players: [
%{player: "Rooster128", score: "0", team: "Storm"},
%{player: "sneakygnome", score: "0", team: "Inferno"},
%{player: "Waldred ", score: "0", team: "Inferno"},
%{player: "HDPTetchy ", score: "0", team: "Storm"},
%{player: "0wnj0o", score: "0", team: "Inferno"},
%{player: "idjit ", score: "0", team: "Storm"},
%{player: "JesusChrist ", score: "0", team: "Storm"},
%{player: "Sofaking--bakeD ", score: "0", team: "Inferno"},
%{player: "saKe ", score: "0", team: "Inferno"},
%{player: "ZurkinWood497", score: "0", team: "Storm"},
%{player: "TerryTC ", score: "0", team: "Inferno"},
%{player: "WankBullet ", score: "0", team: "Storm"},
%{player: "CyClones", score: "0", team: "Inferno"},
%{player: "huntergirl10", score: "0", team: "Storm"},
%{player: "ChocoTaco", score: "0", team: "Inferno"},
%{player: "Dirk", score: "0", team: "Storm"},
%{player: "Krell", score: "0", team: "Storm"},
%{player: "high5slayer", score: "0", team: "Inferno"},
%{player: "Red Fraction ", score: "0", team: "Inferno"},
%{player: "-MaLice--", score: "0", team: "Storm"},
%{player: "wiltedflower ", score: "0", team: "Inferno"},
%{player: "Glarm ", score: "0", team: "Storm"},
%{player: "AlphaSentinel", score: "0", team: "Inferno"},
%{player: "The-Punisher ", score: "0", team: "Storm"},
%{player: "2SmOkeD", score: "0", team: "Inferno"},
%{player: "iPrecision", score: "0", team: "Storm"},
%{player: "Halo 2 ", score: "0", team: "Storm"},
%{player: "Sami-FIN ", score: "0", team: "Inferno"},
%{player: "rileygarbels", score: "0", team: "Storm"}
],
server_description: "This server is using bots that are adapted to playing Classic. http://tribes2bots.byethost4.com/forum/index.php?topic=57.msg234",
server_name: "Classic Bots Server",
team_count: 2,
teams: [%{name: "Storm", score: "0"}, %{name: "Inferno", score: "0"}]
}
"""
defstruct [
server_status: :offline,
server_name: "",
game_type: "",
mission_type: "",
map_name: "",
player_count: 0,
max_player_count: 0,
bot_count: 0,
server_description: "",
team_count: 0,
teams: [],
players: []
]
end

View file

@ -0,0 +1,94 @@
defmodule T2ServerQuery.UdpServer do
@moduledoc """
Documentation for `UdpServer`.
"""
require Logger
alias T2ServerQuery.PacketParser
@doc """
Perform a server query.
Results should be in the form of a tuple
- {:ok, %T2ServerQuery.QueryResult{}}
- {:error, %T2ServerQuery.QueryResult{}}
Querying a Tribes 2 server actually requires sending 2 different packets to the server where the first byte is denoting what we're asking for in response. The first is called the 'info' packet which doesnt contain much more then the server name. The second is called the 'status' packet which contains all the meat and potatoes.
## Examples
iex> T2ServerQuery.UdpServer.query("35.239.88.241")
{:ok,
%T2ServerQuery.QueryResult{
bot_count: 0,
game_type: "Classic",
map_name: "Canker",
max_player_count: 64,
mission_type: "LakRabbit",
player_count: 0,
players: [%{}],
server_description: "Celebrating 20 Years of Tribes2! More information in Discord. <a:playt2.com/discord>playt2.com/discord</a>",
server_name: "Discord PUB",
server_status: :online,
team_count: 1,
teams: [%{name: "Storm", score: 0}]
}}
iex> T2ServerQuery.UdpServer.query("127.0.0.1")
{:error,
%T2ServerQuery.QueryResult{
bot_count: 0,
game_type: "",
map_name: "",
max_player_count: 0,
mission_type: "",
player_count: 0,
players: [],
server_description: "Host unreachable, timed out.",
server_name: "127.0.0.1:28000",
server_status: :offline,
team_count: 0,
teams: []
}}
"""
def query(server_ip, port \\ 28_000, timeout \\ 3_500) do
Logger.info "query: #{server_ip}"
{:ok, socket} = :gen_udp.open(0, [:binary, {:active, false}])
# Convert a string ip from "127.0.0.1" into {127, 0, 0, 1}
{:ok, s_ip} = server_ip
|> to_charlist()
|> :inet.parse_address()
qry_info_packet = <<14, 2, 1, 2, 3, 4>>
qry_status_packet = <<18, 2, 1, 2, 3, 4>>
# Requst info packet
:gen_udp.send(socket, s_ip, port, qry_info_packet)
hex_info_packet = :gen_udp.recv(socket, 0, timeout)
|> handle_udp_response(server_ip, port)
# Request status packet
:gen_udp.send(socket, s_ip, port, qry_status_packet)
hex_status_packet = :gen_udp.recv(socket, 0, timeout)
|> handle_udp_response(server_ip, port)
# Combine and parse results
PacketParser.init(hex_info_packet, hex_status_packet)
end
defp handle_udp_response({:ok, {_ip, _port, packet}}, _server_ip, _port) do
packet
|> Base.encode16
end
defp handle_udp_response({:error, :timeout}, server_ip, port) do
Logger.error "TIMEOUT --> #{server_ip}:#{port}"
{:error, "#{server_ip}:#{port}"}
end
end

27
mix.exs Normal file
View file

@ -0,0 +1,27 @@
defmodule T2ServerQuery.MixProject do
use Mix.Project
def project do
[
app: :t2_server_query,
version: "0.1.0",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:credo, "~> 1.5"}
]
end
end

6
mix.lock Normal file
View file

@ -0,0 +1,6 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
}

View file

@ -0,0 +1,51 @@
defmodule PacketParserTest do
use ExUnit.Case, async: true
alias T2ServerQuery.PacketParser
test "Parse UDP Packet One (Bot Server)" do
hex_info_packet_one = "10 02 01 02 03 04 04 56 45 52 35 33 00 00 00 33 00 00 00 CA 61 00 00 13 43 6C 61 73 73 69 63 20 42 6F 74 73 20 53 65 72 76 65 72"
hex_status_packet_one = "14 02 01 02 03 04 07 43 6C 61 73 73 69 63 10 43 61 70 74 75 72 65 20 74 68 65 20 46 6C 61 67 0F 43 6F 6C 64 20 61 73 20 49 63 65 20 5B 62 5D 21 1D 40 1E B6 09 7F 54 68 69 73 20 73 65 72 76 65 72 20 69 73 20 75 73 69 6E 67 20 62 6F 74 73 20 74 68 61 74 20 61 72 65 20 61 64 61 70 74 65 64 20 74 6F 20 70 6C 61 79 69 6E 67 20 43 6C 61 73 73 69 63 2E 20 68 74 74 70 3A 2F 2F 74 72 69 62 65 73 32 62 6F 74 73 2E 62 79 65 74 68 6F 73 74 34 2E 63 6F 6D 2F 66 6F 72 75 6D 2F 69 6E 64 65 78 2E 70 68 70 3F 74 6F 70 69 63 3D 35 37 2E 6D 73 67 32 33 34 A7 02 32 0A 53 74 6F 72 6D 09 30 0A 49 6E 66 65 72 6E 6F 09 30 0A 32 39 0A 10 0E 52 6F 6F 73 74 65 72 31 32 38 11 09 53 74 6F 72 6D 09 30 0A 10 0E 73 6E 65 61 6B 79 67 6E 6F 6D 65 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 57 61 6C 64 72 65 64 20 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 48 44 50 7C 54 65 74 63 68 79 20 11 09 53 74 6F 72 6D 09 30 0A 10 0E 30 77 6E 6A 30 6F 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 69 64 6A 69 74 20 11 09 53 74 6F 72 6D 09 30 0A 10 0E 4A 65 73 75 73 43 68 72 69 73 74 20 11 09 53 74 6F 72 6D 09 30 0A 10 0E 53 6F 66 61 6B 69 6E 67 2D 7C 2D 62 61 6B 65 44 20 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 73 61 4B 65 20 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 5A 75 72 6B 69 6E 57 6F 6F 64 34 39 37 11 09 53 74 6F 72 6D 09 30 0A 10 0E 54 65 72 72 79 54 43 20 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 57 61 6E 6B 42 75 6C 6C 65 74 20 11 09 53 74 6F 72 6D 09 30 0A 10 0E 43 79 43 6C 6F 6E 65 73 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 68 75 6E 74 65 72 67 69 72 6C 31 30 11 09 53 74 6F 72 6D 09 30 0A 10 0E 43 68 6F 63 6F 54 61 63 6F 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 44 69 72 6B 11 09 53 74 6F 72 6D 09 30 0A 10 0E 4B 72 65 6C 6C 11 09 53 74 6F 72 6D 09 30 0A 10 0E 68 69 67 68 35 73 6C 61 79 65 72 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 52 65 64 20 46 72 61 63 74 69 6F 6E 20 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 2D 4D 61 4C 69 63 65 2D 2D 11 09 53 74 6F 72 6D 09 30 0A 10 0E 77 69 6C 74 65 64 66 6C 6F 77 65 72 20 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 47 6C 61 72 6D 20 11 09 53 74 6F 72 6D 09 30 0A 10 0E 41 6C 70 68 61 53 65 6E 74 69 6E 65 6C 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 5E 54 68 65 2D 50 75 6E 69 73 68 65 72 5E 20 11 09 53 74 6F 72 6D 09 30 0A 10 0E 32 53 6D 4F 6B 65 44 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 69 50 72 65 63 69 73 69 6F 6E 11 09 53 74 6F 72 6D 09 30 0A 10 0E 48 61 6C 6F 20 32 20 11 09 53 74 6F 72 6D 09 30 0A 10 0E 53 61 6D 69 2D 46 49 4E 20 11 09 49 6E 66 65 72 6E 6F 09 30 0A 10 0E 72 69 6C 65 79 67 61 72 62 65 6C 73 11 09 53 74 6F 72 6D 09 30"
{:ok, result} = PacketParser.init(hex_info_packet_one, hex_status_packet_one)
|> T2ServerQuery.log
assert result.server_status == :online
assert result.bot_count == 30
assert result.game_type == "Classic"
assert result.map_name == "Cold as Ice [b]"
assert result.max_player_count == 64
assert result.mission_type == "Capture the Flag"
assert result.player_count == 29
assert length(result.players) == result.player_count
assert List.first(result.players) == %{player: "Rooster128", score: 0, team: "Storm"}
assert result.server_description == "This server is using bots that are adapted to playing Classic. http://tribes2bots.byethost4.com/forum/index.php?topic=57.msg234"
assert result.server_name == "Classic Bots Server"
assert result.team_count == 2
assert List.first(result.teams) == %{name: "Storm", score: 0}
end
test "Parse UDP Packet Two (DiscordPUB Server)" do
hex_info_packet_two = "10 02 01 02 03 04 04 56 45 52 35 33 00 00 00 33 00 00 00 CA 61 00 00 0B 44 69 73 63 6F 72 64 20 50 55 42"
hex_status_packet_two = "14 02 01 02 03 04 07 43 6C 61 73 73 69 63 09 4C 61 6B 52 61 62 62 69 74 08 53 75 6E 44 61 6E 63 65 A1 00 40 00 E5 08 6A 43 65 6C 65 62 72 61 74 69 6E 67 20 32 30 20 59 65 61 72 73 20 6F 66 20 54 72 69 62 65 73 32 21 20 4D 6F 72 65 20 69 6E 66 6F 72 6D 61 74 69 6F 6E 20 69 6E 20 44 69 73 63 6F 72 64 2E 20 3C 61 3A 70 6C 61 79 74 32 2E 63 6F 6D 2F 64 69 73 63 6F 72 64 3E 70 6C 61 79 74 32 2E 63 6F 6D 2F 64 69 73 63 6F 72 64 3C 2F 61 3E 0B 00 31 0A 53 74 6F 72 6D 09 30 0A 30"
{:ok, result} = PacketParser.init(hex_info_packet_two, hex_status_packet_two)
|> T2ServerQuery.log
assert result.server_status == :online
assert result.bot_count == 0
assert result.game_type == "Classic"
assert result.map_name == "SunDance"
assert result.max_player_count == 64
assert result.mission_type == "LakRabbit"
assert result.player_count == 0
assert List.first(result.players) == %{}
assert result.server_description == "Celebrating 20 Years of Tribes2! More information in Discord. <a:playt2.com/discord>playt2.com/discord</a>"
assert result.server_name == "Discord PUB"
assert result.team_count == 1
assert List.first(result.teams) == %{name: "Storm", score: 0}
end
end

View file

@ -0,0 +1,93 @@
defmodule T2ServerQueryTest do
use ExUnit.Case, async: true
alias T2ServerQuery.UdpServer
doctest T2ServerQuery
test "Gracefully handle timeouts and unreachable servers" do
# We know this query is going to timeout, so lets not wait around :)
timeout = 250
port = Enum.random(28_000..28_999)
{:error, result} = T2ServerQuery.UdpServer.query("127.0.0.1", port, timeout)
|> T2ServerQuery.log
assert result.server_status == :offline
assert result.server_description == "Host unreachable, timed out."
assert result.server_name == "127.0.0.1:#{port}"
end
test "Live test a number of Tribes 2 servers" do
tasks = [
Task.async(T2ServerQuery.UdpServer, :query, ["35.239.88.241"]),
Task.async(T2ServerQuery.UdpServer, :query, ["97.99.172.12", 28_001]),
Task.async(T2ServerQuery.UdpServer, :query, ["67.222.138.13"])
]
server_list = Task.yield_many(tasks)
|> Enum.map(fn {task, result} ->
#|> Enum.with_index(fn {task, result}, index ->
# :ok should be returned for each task and result
# assert {task, {:ok, result}} == Enum.at(server_list, index)
test_server_status(result)
end)
end
defp test_server_status({:ok, _}) do
assert true
end
defp test_server_status({:error, _}) do
assert false
end
defp test_server_status(nil) do
assert false
end
end
#qry_test = T2ServerQuery.UdpServer.query("127.0.0.1")
#IO.inspect qry_test
#qry_test2 = T2ServerQuery.UdpServer.query("35.239.88.241")
#IO.inspect qry_test2
# tasks = [
# Task.async(T2ServerQuery.UdpServer, :query, ["127.0.0.1"]),
# Task.async(T2ServerQuery.UdpServer, :query, ["35.239.88.241"]),
# Task.async(T2ServerQuery.UdpServer, :query, ["97.99.172.12", 28001]),
# Task.async(T2ServerQuery.UdpServer, :query, ["67.222.138.13"]),
# Task.async(T2ServerQuery.UdpServer, :query, ["91.55.51.94"]),
# ]
# IO.inspect Task.yield_many(tasks)
# task0 = Task.async(T2ServerQuery.UdpServer, :query, ["127.0.0.1"])
# task1 = Task.async(T2ServerQuery.UdpServer, :query, ["35.239.88.241"])
# task2 = Task.async(T2ServerQuery.UdpServer, :query, ["97.99.172.12", 28001])
# task3 = Task.async(T2ServerQuery.UdpServer, :query, ["67.222.138.13"])
# task4 = Task.async(T2ServerQuery.UdpServer, :query, ["91.55.51.94"])
# # res4 = Task.await(task4)
# # IO.inspect res4.server_name
# res0 = Task.await(task0)
# res1 = Task.await(task1)
# res2 = Task.await(task2)
# res3 = Task.await(task3)
# res4 = Task.await(task4)
# IO.inspect res1.server_name
# IO.inspect res2.server_name
# IO.inspect res3.server_name
# IO.inspect res4.server_name
# T2ServerQuery.UdpServer.query("35.239.88.241")
# T2ServerQuery.UdpServer.query("97.99.172.12", 28001)
# T2ServerQuery.UdpServer.query("67.222.138.13")
# T2ServerQuery.UdpServer.query("91.55.51.94")

1
test/test_helper.exs Normal file
View file

@ -0,0 +1 @@
ExUnit.start()