Comparison

util/prosodyctl/check.lua @ 12357:cd11d7c4af8b

util.prosodyctl: check turn: New command to verify STUN/TURN service is operational
author Matthew Wild <mwild1@gmail.com>
date Fri, 04 Mar 2022 15:28:44 +0000
parent 12233:e4530bdbf5f3
child 12362:0fd58f54d653
comparison
equal deleted inserted replaced
12356:0f77e28df5c8 12357: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