util/prosodyctl/check.lua
changeset 12361 cd11d7c4af8b
parent 12237 e4530bdbf5f3
child 12366 0fd58f54d653
equal deleted inserted replaced
12360:0f77e28df5c8 12361:cd11d7c4af8b
    58 		end
    58 		end
    59 	end
    59 	end
    60 	return false, "Probe endpoint did not return a success status";
    60 	return false, "Probe endpoint did not return a success status";
    61 end
    61 end
    62 
    62 
       
    63 local function check_turn_service(turn_service)
       
    64 	local stun = require "net.stun";
       
    65 
       
    66 	-- Create UDP socket for communication with the server
       
    67 	local sock = assert(require "socket".udp());
       
    68 	sock:setsockname("*", 0);
       
    69 	sock:setpeername(turn_service.host, turn_service.port);
       
    70 	sock:settimeout(10);
       
    71 
       
    72 	-- Helper function to receive a packet
       
    73 	local function receive_packet()
       
    74 		local raw_packet, err = sock:receive();
       
    75 		if not raw_packet then
       
    76 			return nil, err;
       
    77 		end
       
    78 		return stun.new_packet():deserialize(raw_packet);
       
    79 	end
       
    80 
       
    81 	local result = { warnings = {} };
       
    82 
       
    83 	-- Send a "binding" query, i.e. a request for our external IP/port
       
    84 	local bind_query = stun.new_packet("binding", "request");
       
    85 	bind_query:add_attribute("software", "prosodyctl check turn");
       
    86 	sock:send(bind_query:serialize());
       
    87 
       
    88 	local bind_result, err = receive_packet();
       
    89 	if not bind_result then
       
    90 		result.error = "No STUN response: "..err;
       
    91 		return result;
       
    92 	elseif bind_result:is_err_resp() then
       
    93 		result.error = ("STUN server returned error: %d (%s)"):format(bind_result:get_error());
       
    94 		return result;
       
    95 	elseif not bind_result:is_success_resp() then
       
    96 		result.error = ("Unexpected STUN response: %d (%s)"):format(bind_result:get_type());
       
    97 		return result;
       
    98 	end
       
    99 
       
   100 	result.external_ip = bind_result:get_xor_mapped_address();
       
   101 	if not result.external_ip then
       
   102 		result.error = "STUN server did not return an address";
       
   103 		return result;
       
   104 	end
       
   105 
       
   106 	-- Send a TURN "allocate" request. Expected to fail due to auth, but
       
   107 	-- necessary to obtain a valid realm/nonce from the server.
       
   108 	local pre_request = stun.new_packet("allocate", "request");
       
   109 	sock:send(pre_request:serialize());
       
   110 
       
   111 	local pre_result, err = receive_packet();
       
   112 	if not pre_result then
       
   113 		result.error = "No initial TURN response: "..err;
       
   114 		return result;
       
   115 	elseif pre_result:is_success_resp() then
       
   116 		result.error = "TURN server does not have authentication enabled";
       
   117 		return result;
       
   118 	end
       
   119 
       
   120 	local realm = pre_result:get_attribute("realm");
       
   121 	local nonce = pre_result:get_attribute("nonce");
       
   122 
       
   123 	if not realm then
       
   124 		table.insert(result.warnings, "TURN server did not return an authentication realm");
       
   125 	end
       
   126 	if not nonce then
       
   127 		table.insert(result.warnings, "TURN server did not return a nonce");
       
   128 	end
       
   129 
       
   130 	-- Use the configured secret to obtain temporary user/pass credentials
       
   131 	local turn_user, turn_pass = stun.get_user_pass_from_secret(turn_service.secret);
       
   132 
       
   133 	-- Send a TURN allocate request, will fail if auth is wrong
       
   134 	local alloc_request = stun.new_packet("allocate", "request");
       
   135 	alloc_request:add_requested_transport("udp");
       
   136 	alloc_request:add_attribute("username", turn_user);
       
   137 	if realm then
       
   138 		alloc_request:add_attribute("realm", realm);
       
   139 	end
       
   140 	if nonce then
       
   141 		alloc_request:add_attribute("nonce", nonce);
       
   142 	end
       
   143 	local key = stun.get_long_term_auth_key(realm or turn_service.host, turn_user, turn_pass);
       
   144 	alloc_request:add_message_integrity(key);
       
   145 	sock:send(alloc_request:serialize());
       
   146 
       
   147 	-- Check the response
       
   148 	local alloc_response, err = receive_packet();
       
   149 	if not alloc_response then
       
   150 		result.error = "TURN server did not response to allocation request: "..err;
       
   151 		return;
       
   152 	elseif alloc_response:is_err_resp() then
       
   153 		result.error = ("TURN allocation failed: %d (%s)"):format(alloc_response:get_error());
       
   154 		return result;
       
   155 	elseif not alloc_response:is_success_resp() then
       
   156 		result.error = ("Unexpected TURN response: %d (%s)"):format(alloc_response:get_type());
       
   157 		return result;
       
   158 	end
       
   159 
       
   160 	-- No errors? Ok!
       
   161 
       
   162 	return result;
       
   163 end
       
   164 
    63 local function skip_bare_jid_hosts(host)
   165 local function skip_bare_jid_hosts(host)
    64 	if jid_split(host) then
   166 	if jid_split(host) then
    65 		-- See issue #779
   167 		-- See issue #779
    66 		return false;
   168 		return false;
    67 	end
   169 	end
    78 	local set = require "util.set";
   180 	local set = require "util.set";
    79 	local it = require "util.iterators";
   181 	local it = require "util.iterators";
    80 	local ok = true;
   182 	local ok = true;
    81 	local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end
   183 	local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end
    82 	local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end
   184 	local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end
    83 	if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs" or what == "connectivity") then
   185 	if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs" or what == "connectivity" or what == "turn") then
    84 		show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs', 'disabled' or 'connectivity'.", what);
   186 		show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs', 'disabled', 'turn' or 'connectivity'.", what);
    85 		show_warning("Note: The connectivity check will connect to a remote server.");
   187 		show_warning("Note: The connectivity check will connect to a remote server.");
    86 		return 1;
   188 		return 1;
    87 	end
   189 	end
    88 	if not what or what == "disabled" then
   190 	if not what or what == "disabled" then
    89 		local disabled_hosts_set = set.new();
   191 		local disabled_hosts_set = set.new();
   918 			print()
  1020 			print()
   919 		end
  1021 		end
   920 		print("Note: The connectivity check only checks the reachability of the domain.")
  1022 		print("Note: The connectivity check only checks the reachability of the domain.")
   921 		print("Note: It does not ensure that the check actually reaches this specific prosody instance.")
  1023 		print("Note: It does not ensure that the check actually reaches this specific prosody instance.")
   922 	end
  1024 	end
       
  1025 
       
  1026 	if what == "turn" then
       
  1027 		local turn_enabled_hosts = {};
       
  1028 		local turn_services = {};
       
  1029 
       
  1030 		for host in enabled_hosts() do
       
  1031 			local has_external_turn = modulemanager.get_modules_for_host(host):contains("turn_external");
       
  1032 			if has_external_turn then
       
  1033 				table.insert(turn_enabled_hosts, host);
       
  1034 				local turn_host = configmanager.get(host, "turn_external_host") or host;
       
  1035 				local turn_port = configmanager.get(host, "turn_external_port") or 3478;
       
  1036 				local turn_secret = configmanager.get(host, "turn_external_secret");
       
  1037 				if not turn_secret then
       
  1038 					print("Error: Your configuration is missing a turn_external_secret for "..host);
       
  1039 					print("Error: TURN will not be advertised for this host.");
       
  1040 					ok = false;
       
  1041 				else
       
  1042 					local turn_id = ("%s:%d"):format(turn_host, turn_port);
       
  1043 					if turn_services[turn_id] and turn_services[turn_id].secret ~= turn_secret then
       
  1044 						print("Error: Your configuration contains multiple differing secrets");
       
  1045 						print("       for the TURN service at "..turn_id.." - we will only test one.");
       
  1046 					elseif not turn_services[turn_id] then
       
  1047 						turn_services[turn_id] = {
       
  1048 							host = turn_host;
       
  1049 							port = turn_port;
       
  1050 							secret = turn_secret;
       
  1051 						};
       
  1052 					end
       
  1053 				end
       
  1054 			end
       
  1055 		end
       
  1056 
       
  1057 		if what == "turn" then
       
  1058 			local count = it.count(pairs(turn_services));
       
  1059 			if count == 0 then
       
  1060 				print("Error: Unable to find any TURN services configured. Enable mod_turn_external!");
       
  1061 			else
       
  1062 				print("Identified "..tostring(count).." TURN services.");
       
  1063 				print("");
       
  1064 			end
       
  1065 		end
       
  1066 
       
  1067 		for turn_id, turn_service in pairs(turn_services) do
       
  1068 			print("Testing "..turn_id.."...");
       
  1069 
       
  1070 			local result = check_turn_service(turn_service);
       
  1071 			if #result.warnings > 0 then
       
  1072 				print(("%d warnings:\n\n    "):format(#result.warnings));
       
  1073 				print(table.concat(result.warnings, "\n    "));
       
  1074 			end
       
  1075 			if result.error then
       
  1076 				print("Error: "..result.error.."\n");
       
  1077 				ok = false;
       
  1078 			else
       
  1079 				print("Success!\n");
       
  1080 			end
       
  1081 		end
       
  1082 	end
       
  1083 
   923 	if not ok then
  1084 	if not ok then
   924 		print("Problems found, see above.");
  1085 		print("Problems found, see above.");
   925 	else
  1086 	else
   926 		print("All checks passed, congratulations!");
  1087 		print("All checks passed, congratulations!");
   927 	end
  1088 	end