Software /
code /
prosody
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 |