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(); |
1002 print() |
1104 print() |
1003 end |
1105 end |
1004 print("Note: The connectivity check only checks the reachability of the domain.") |
1106 print("Note: The connectivity check only checks the reachability of the domain.") |
1005 print("Note: It does not ensure that the check actually reaches this specific prosody instance.") |
1107 print("Note: It does not ensure that the check actually reaches this specific prosody instance.") |
1006 end |
1108 end |
|
1109 |
|
1110 if what == "turn" then |
|
1111 local turn_enabled_hosts = {}; |
|
1112 local turn_services = {}; |
|
1113 |
|
1114 for host in enabled_hosts() do |
|
1115 local has_external_turn = modulemanager.get_modules_for_host(host):contains("turn_external"); |
|
1116 if has_external_turn then |
|
1117 table.insert(turn_enabled_hosts, host); |
|
1118 local turn_host = configmanager.get(host, "turn_external_host") or host; |
|
1119 local turn_port = configmanager.get(host, "turn_external_port") or 3478; |
|
1120 local turn_secret = configmanager.get(host, "turn_external_secret"); |
|
1121 if not turn_secret then |
|
1122 print("Error: Your configuration is missing a turn_external_secret for "..host); |
|
1123 print("Error: TURN will not be advertised for this host."); |
|
1124 ok = false; |
|
1125 else |
|
1126 local turn_id = ("%s:%d"):format(turn_host, turn_port); |
|
1127 if turn_services[turn_id] and turn_services[turn_id].secret ~= turn_secret then |
|
1128 print("Error: Your configuration contains multiple differing secrets"); |
|
1129 print(" for the TURN service at "..turn_id.." - we will only test one."); |
|
1130 elseif not turn_services[turn_id] then |
|
1131 turn_services[turn_id] = { |
|
1132 host = turn_host; |
|
1133 port = turn_port; |
|
1134 secret = turn_secret; |
|
1135 }; |
|
1136 end |
|
1137 end |
|
1138 end |
|
1139 end |
|
1140 |
|
1141 if what == "turn" then |
|
1142 local count = it.count(pairs(turn_services)); |
|
1143 if count == 0 then |
|
1144 print("Error: Unable to find any TURN services configured. Enable mod_turn_external!"); |
|
1145 else |
|
1146 print("Identified "..tostring(count).." TURN services."); |
|
1147 print(""); |
|
1148 end |
|
1149 end |
|
1150 |
|
1151 for turn_id, turn_service in pairs(turn_services) do |
|
1152 print("Testing "..turn_id.."..."); |
|
1153 |
|
1154 local result = check_turn_service(turn_service); |
|
1155 if #result.warnings > 0 then |
|
1156 print(("%d warnings:\n\n "):format(#result.warnings)); |
|
1157 print(table.concat(result.warnings, "\n ")); |
|
1158 end |
|
1159 if result.error then |
|
1160 print("Error: "..result.error.."\n"); |
|
1161 ok = false; |
|
1162 else |
|
1163 print("Success!\n"); |
|
1164 end |
|
1165 end |
|
1166 end |
|
1167 |
1007 if not ok then |
1168 if not ok then |
1008 print("Problems found, see above."); |
1169 print("Problems found, see above."); |
1009 else |
1170 else |
1010 print("All checks passed, congratulations!"); |
1171 print("All checks passed, congratulations!"); |
1011 end |
1172 end |