From 8333c7b00d0efcb62ef2853e18e2e536de57137b Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Fri, 4 Sep 2026 20:52:47 -0500 Subject: [PATCH] Implemented unprivileged UDP based ICMP echo requests for pinging game server hosts --- lib/worker.rb | 67 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/lib/worker.rb b/lib/worker.rb index 66df02e..1a798d6 100644 --- a/lib/worker.rb +++ b/lib/worker.rb @@ -29,11 +29,15 @@ module W3DHubLauncher @requests = [] @settings = 0# Settings.new + @game_servers = [] + @host_pings = {} # connect to and monitor GSH web service @threads << Thread.new { game_server_hub_websocket } # connect to and monitor Backend web service @threads << Thread.new { backend_websocket } + # poke game servers to ascertain their ping relative to the local machine + @threads << Thread.new { ping_game_servers } @w3dhub_api = W3DHubLauncher::W3DHubApi.new @@ -45,11 +49,11 @@ module W3DHubLauncher json = JSON.parse(data) query = Request::Query.new(type: json["type"].to_sym, request_id: json["request_id"], data: json["data"]) - pp [:server_incoming, data, query] + # pp [:server_incoming, data, query] if respond_to?(query.type) response = send(query.type, query) - pp [:server_to_client, response] + # pp [:server_to_client, response] payload = { status: response.status, request_id: response.request_id, data: response.result.data, error: response.result.error }.to_json socket.puts(payload) end @@ -68,9 +72,56 @@ module W3DHubLauncher def backend_websocket end + def ping_game_servers + socket = Socket.open(Socket::AF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_ICMP) + socket.setsockopt(:SOCKET, :TIMESTAMP, true) + + Async do |task| + while true + echo_requests = {} + + # Send pings to each unique server host + @game_servers.map(&:address).uniq.each do |server_address| + address = Socket.sockaddr_in(0, server_address) + sequence_id = Digest::SHA256.hexdigest("#{server_address}-#{Time.now.iso8601}") + + # ICMP Echo Request, ICMP Code, , , , Data + msg = [8, 0, 0, 0, sequence_id].flatten.pack("C2n2A64") + echo_requests[sequence_id] = { send_time: Time.now, address: server_address, replied: false } + socket.send(msg, 0, address) + end + + # Receive replies until timeout + task.with_timeout(3) do + while(echo_requests.values.any? { |v| v[:replied] == false }) + response, sender_address, flags, *controls = socket.recvmsg(256) + _type, _code, _identifier, _sequence, data = response.unpack("C2n2A64") + + if (request = echo_requests[data])# && request[:address] == sender_address. + request[:replied] = true + round_trip_time = ((controls.last.timestamp - request[:send_time]) * 1000.0).round + @host_pings[request[:address]] = round_trip_time + + # puts "#{request[:address]}: #{round_trip_time}ms" + # else + # # packet not for us, or it got mangled in transit. + end + end + rescue Async::TimeoutError + # puts "Timed out waiting for: #{echo_requests.values.select { |v| v[:replied] == false }.map { |v| v[:address] }.join(', ')}" + end + + sleep 5 + end + end + + ensure + socket&.close + end + # Send request to server def request(query) - pp [:client_request, query] + # pp [:client_request, query] payload = { type: query.type, request_id: query.request_id, data: query.data }.to_json if respond_to?(query.type) @@ -82,11 +133,11 @@ module W3DHubLauncher def service data = @socket.read_nonblock(1_048_576) # 1 MB - pp [:CLIENT, data] + # pp [:CLIENT, data] json = JSON.parse(data) request = W3DHubLauncher::Worker::Request.requests.find { |r| r.request_id == json["request_id"] } - pp [json, request] + # pp [json, request] return unless request CyberarmEngine::Window.instance&.add_to_queue(proc { @@ -101,7 +152,7 @@ module W3DHubLauncher def deliver_response(result, query) response = Response.new(result.okay? ? Request::STATUS_COMPLETE : Request::STATUS_ERROR, query.request_id, result) - pp response + # pp response response end @@ -252,6 +303,10 @@ module W3DHubLauncher response = fetch_url(query) + if response.result.okay? + @game_servers = JSON.parse(response.result.data).map { |server| Worker::Api::GameServer.new(server) } + end + deliver_response(response.result, query) end