diff options
| author | onefang | 2019-06-25 15:08:34 +1000 |
|---|---|---|
| committer | onefang | 2019-06-25 15:08:34 +1000 |
| commit | 22fc09cfd2305850063d2bbdd3af76237caf2e73 (patch) | |
| tree | 87765825e2c2425ba00bcd26dfeb14919e39d539 /mirror-checker.lua | |
| parent | Git ignore the results. (diff) | |
| download | apt-panopticon-22fc09cfd2305850063d2bbdd3af76237caf2e73.zip apt-panopticon-22fc09cfd2305850063d2bbdd3af76237caf2e73.tar.gz apt-panopticon-22fc09cfd2305850063d2bbdd3af76237caf2e73.tar.bz2 apt-panopticon-22fc09cfd2305850063d2bbdd3af76237caf2e73.tar.xz | |
Add the actual source code, and the basic documentation.
Still need to write the help output.
Diffstat (limited to 'mirror-checker.lua')
| -rwxr-xr-x | mirror-checker.lua | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/mirror-checker.lua b/mirror-checker.lua new file mode 100755 index 0000000..34b2ec8 --- /dev/null +++ b/mirror-checker.lua | |||
| @@ -0,0 +1,420 @@ | |||
| 1 | #!/usr/bin/env luajit | ||
| 2 | |||
| 3 | |||
| 4 | local args = {...} | ||
| 5 | |||
| 6 | |||
| 7 | verbosity = 0 | ||
| 8 | keep = false | ||
| 9 | options = | ||
| 10 | { | ||
| 11 | referenceSite = | ||
| 12 | { | ||
| 13 | typ = "string", | ||
| 14 | help = "", | ||
| 15 | value = "pkgmaster.devuan.org", | ||
| 16 | }, | ||
| 17 | tests = | ||
| 18 | { | ||
| 19 | typ = "table", | ||
| 20 | help = "", | ||
| 21 | value = | ||
| 22 | { | ||
| 23 | "IPv4", | ||
| 24 | "IPv6", | ||
| 25 | -- "ftp", | ||
| 26 | "http", | ||
| 27 | "https", | ||
| 28 | -- "rsync", | ||
| 29 | "DNS-RR", | ||
| 30 | "Protocol", | ||
| 31 | -- "URL-Sanity", | ||
| 32 | -- "Integrity", | ||
| 33 | -- "Updated", | ||
| 34 | }, | ||
| 35 | }, | ||
| 36 | } | ||
| 37 | |||
| 38 | local defaultURL = {scheme = "http"} | ||
| 39 | local download = "wget -np -N -r -P results " | ||
| 40 | local releases = {"jessie", "ascii", "beowulf", "ceres"} | ||
| 41 | local releaseFiles = | ||
| 42 | { | ||
| 43 | -- Release file. | ||
| 44 | "/Release", | ||
| 45 | "/InRelease", | ||
| 46 | "/main/binary-all/Packages.gz", | ||
| 47 | -- Contents files. | ||
| 48 | "/main/Contents-all.gz", | ||
| 49 | "/main/Contents-amd64.gz", | ||
| 50 | "/main/Contents-arm64.gz", | ||
| 51 | "-security/main/Contents-all.gz", | ||
| 52 | "-security/main/Contents-amd64.gz", | ||
| 53 | "-security/main/Contents-arm64.gz", | ||
| 54 | } | ||
| 55 | local referenceDebs = | ||
| 56 | { | ||
| 57 | -- Devuan package. NOTE this one likely should not get redirected, but that's more a warning than an error. | ||
| 58 | "merged/pool/DEVUAN/main/d/desktop-base/desktop-base_2.0.3_all.deb", | ||
| 59 | -- Debian package. | ||
| 60 | "merged/pool/DEBIAN/main/d/dash/dash_0.5.8-2.4_amd64.deb", | ||
| 61 | -- Debian security package. NOTE this one should always be redirected? | ||
| 62 | "merged/pool/DEBIAN-SECURITY/updates/main/a/apt/apt-transport-https_1.4.9_amd64.deb", | ||
| 63 | } | ||
| 64 | local arg = {} | ||
| 65 | local sendArgs = "" | ||
| 66 | local log | ||
| 67 | |||
| 68 | |||
| 69 | local socket = require 'socket' | ||
| 70 | local ftp = require 'socket.ftp' | ||
| 71 | local http = require 'socket.http' | ||
| 72 | local url = require 'socket.url' | ||
| 73 | |||
| 74 | |||
| 75 | -- Use this to print a table. | ||
| 76 | printTable = function (table, space, name) | ||
| 77 | print(space .. name .. ": ") | ||
| 78 | print(space .. "{") | ||
| 79 | printTableSub(table, space .. " ") | ||
| 80 | print(space .. "}") | ||
| 81 | end | ||
| 82 | printTableSub = function (table, space) | ||
| 83 | for k, v in pairs(table) do | ||
| 84 | if type(v) == "table" then | ||
| 85 | printTable(v, space, k) | ||
| 86 | elseif type(v) == "string" then | ||
| 87 | print(space .. k .. ': "' .. v .. '";') | ||
| 88 | elseif type(v) == "function" then | ||
| 89 | print(space .. "function " .. k .. "();") | ||
| 90 | elseif type(v) == "userdata" then | ||
| 91 | print(space .. "userdata " .. k .. ";") | ||
| 92 | elseif type(v) == "boolean" then | ||
| 93 | if (v) then | ||
| 94 | print(space .. "boolean " .. k .. " TRUE ;") | ||
| 95 | else | ||
| 96 | print(space .. "boolean " .. k .. " FALSE ;") | ||
| 97 | end | ||
| 98 | else | ||
| 99 | print(space .. k .. ": " .. v .. ";") | ||
| 100 | end | ||
| 101 | end | ||
| 102 | end | ||
| 103 | |||
| 104 | local log = function(v, t, s) | ||
| 105 | if v <= verbosity then | ||
| 106 | if 3 <= verbosity then t = os.date() .. " " .. t end | ||
| 107 | print(t .. ": " .. s) | ||
| 108 | end | ||
| 109 | if nil ~= log then | ||
| 110 | log:write(os.date() .. " " .. t .. ": " .. s .. "/n") | ||
| 111 | log:flush() | ||
| 112 | end | ||
| 113 | end | ||
| 114 | local D = function(s) log(3, "DEBUG ", s) end | ||
| 115 | local I = function(s) log(2, "INFO ", s) end | ||
| 116 | local W = function(s) log(1, "WARNING ", s) end | ||
| 117 | local E = function(s) log(0, "ERROR ", s) end | ||
| 118 | local C = function(s) log(-1, "CRITICAL", s) end | ||
| 119 | |||
| 120 | local testing = function(t) | ||
| 121 | for i, v in pairs(options.tests.value) do | ||
| 122 | if t == v then return true end | ||
| 123 | end | ||
| 124 | return false | ||
| 125 | end | ||
| 126 | |||
| 127 | local checkExes = function (exe) | ||
| 128 | local count = io.popen('ps x | grep "' .. exe .. '" | grep -v " grep " | wc -l'):read("*l") | ||
| 129 | D(count .. " " .. exe .. " commands left.") | ||
| 130 | return tonumber(count) | ||
| 131 | end | ||
| 132 | |||
| 133 | |||
| 134 | local IP = {} | ||
| 135 | local gatherIPs = function (host) | ||
| 136 | if nil == IP[host] then | ||
| 137 | local IPs | ||
| 138 | local dig = io.popen('dig +keepopen +noall +nottlid +answer ' .. host .. ' A ' .. host .. ' AAAA ' .. host .. ' CNAME ' .. host .. ' SRV | sort | uniq') | ||
| 139 | repeat | ||
| 140 | IPs = dig:read("*l") | ||
| 141 | if nil ~= IPs then | ||
| 142 | for k, t, v in IPs:gmatch("([%w_%-%.]*)%.%s*IN%s*(%a*)%s*(.*)") do | ||
| 143 | if "." == v:sub(-1, -1) then v = v:sub(1, -2) end | ||
| 144 | if nil == IP[k] then IP[k] = {} end | ||
| 145 | IP[k][v] = t | ||
| 146 | end | ||
| 147 | end | ||
| 148 | until nil == IPs | ||
| 149 | end | ||
| 150 | end | ||
| 151 | |||
| 152 | -- Returns FTP directory listing | ||
| 153 | local nlst = function (u) | ||
| 154 | local t = {} | ||
| 155 | local p = url.parse(u) | ||
| 156 | p.command = "nlst" | ||
| 157 | p.sink = ltn12.sink.table(t) | ||
| 158 | local r, e = ftp.get(p) | ||
| 159 | return r and table.concat(t), e | ||
| 160 | end | ||
| 161 | |||
| 162 | checkURL = function (host, URL, r) | ||
| 163 | if nil == r then r = 0 end | ||
| 164 | local check = "Checking file" | ||
| 165 | if 0 < r then | ||
| 166 | check = "Redirecting to" | ||
| 167 | -- checkIP(host) | ||
| 168 | end | ||
| 169 | if 10 < r then | ||
| 170 | E("too many redirects! " .. check .. " " .. host .. " -> " .. URL) | ||
| 171 | return | ||
| 172 | end | ||
| 173 | local PU = url.parse(URL, defaultURL) | ||
| 174 | D(" " .. PU.scheme .. " :// " .. check .. " " .. host .. " -> " .. URL) | ||
| 175 | if not testing(PU.scheme) then D("not testing " .. PU.scheme .. " " .. host .. " -> " .. URL); return end | ||
| 176 | local hd = {Host = host} | ||
| 177 | local p, c, h, s = http.request{method = "HEAD", redirect = false, url = URL, headers = hd} | ||
| 178 | if nil == p then E(c .. "! " .. check .. " " .. host .. " -> " .. URL) else | ||
| 179 | l = h.location | ||
| 180 | if nil ~= l then | ||
| 181 | local pu = url.parse(l, defaultURL) | ||
| 182 | if l == URL then | ||
| 183 | E("redirect loop! " .. check .. " " .. host .. " -> " .. URL) | ||
| 184 | else | ||
| 185 | if nil == pu.host then | ||
| 186 | W("no location host! " .. check .. " " .. host .. " -> " .. URL .. " -> " .. l) | ||
| 187 | checkURL(host, PU.scheme .. "://" .. PU.host .. l, r + 1) | ||
| 188 | else | ||
| 189 | if testing("Protocol") and pu.scheme ~= PU.scheme then | ||
| 190 | W("protocol changed during redirect! " .. check .. " " .. host .. " -> " .. URL .. " -> " .. l) | ||
| 191 | end | ||
| 192 | checkURL(pu.host, l, r + 1) | ||
| 193 | end | ||
| 194 | end | ||
| 195 | end | ||
| 196 | end | ||
| 197 | end | ||
| 198 | |||
| 199 | local checkPaths = function (host, ip, path) | ||
| 200 | I(" Checking IP " .. host .. " -> " .. ip .. " " .. path) | ||
| 201 | for i, s in pairs(referenceDebs) do | ||
| 202 | if testing("http") then checkURL(host, "http://" .. ip .. path .. s) end | ||
| 203 | if testing("https") then checkURL(host, "https://" .. ip .. path .. s) end | ||
| 204 | end | ||
| 205 | |||
| 206 | for i, s in pairs(releases) do | ||
| 207 | for j, k in pairs(releaseFiles) do | ||
| 208 | if testing("http") then checkURL(host, "http://" .. ip .. path .. "merged/dists/" .. s .. k) end | ||
| 209 | if testing("https") then checkURL(host, "https://" .. ip .. path .. "merged/dists/" .. s .. k) end | ||
| 210 | end | ||
| 211 | end | ||
| 212 | end | ||
| 213 | |||
| 214 | local execute = function (s) | ||
| 215 | D("executing " .. s) | ||
| 216 | os.execute(s) | ||
| 217 | end | ||
| 218 | |||
| 219 | forkIP = function (orig, host) | ||
| 220 | if nil == host then host = orig end | ||
| 221 | local po = url.parse("http://" .. orig, defaultURL) | ||
| 222 | local ph = url.parse("http://" .. host, defaultURL) | ||
| 223 | gatherIPs(ph.host) | ||
| 224 | for k, v in pairs(IP[ph.host]) do | ||
| 225 | if v == "A" then | ||
| 226 | if testing("IPv4") then execute("sleep 1; ionice -c3 ./mirror-checker.lua " .. sendArgs .. " " .. host .. " " .. k .. " &") end | ||
| 227 | elseif v == "AAAA" then | ||
| 228 | if testing("IPv6") then execute("sleep 1; ionice -c3 ./mirror-checker.lua " .. sendArgs .. " " .. host .. " [" .. k .. "] &") end | ||
| 229 | elseif v == "CNAME" then | ||
| 230 | forkIP(orig, k) | ||
| 231 | end | ||
| 232 | end | ||
| 233 | end | ||
| 234 | |||
| 235 | checkIP = function (orig, host, path, ip) | ||
| 236 | if nil ~= ip then | ||
| 237 | checkPaths(orig, ip, path) | ||
| 238 | else | ||
| 239 | D("checkIP " .. orig .. " " .. host) | ||
| 240 | gatherIPs(host) | ||
| 241 | for k, v in pairs(IP[host]) do | ||
| 242 | if v == "A" then | ||
| 243 | if testing("IPv4") then checkPaths(orig, k, path) end | ||
| 244 | elseif v == "AAAA" then | ||
| 245 | if testing("IPv6") then checkPaths(orig, "[" .. k .. "]", path) end | ||
| 246 | elseif v == "CNAME" then | ||
| 247 | checkIP(orig, k, path) | ||
| 248 | end | ||
| 249 | end | ||
| 250 | end | ||
| 251 | end | ||
| 252 | |||
| 253 | local checkHost = function (host, path, ip) | ||
| 254 | if nil == path then path = "/" else | ||
| 255 | if "/" == path:sub(-1, -1) then | ||
| 256 | W("slash at end of BaseURL in mirror_list.txt! " .. host .. " " .. path) | ||
| 257 | else | ||
| 258 | path = path .. "/" | ||
| 259 | end | ||
| 260 | end | ||
| 261 | checkIP(host, host, path, ip) | ||
| 262 | end | ||
| 263 | |||
| 264 | local downloads = function (host, URL, IP) | ||
| 265 | if nil == URL then URL = "/" end | ||
| 266 | if nil == IP then IP = "" else IP = "-" .. IP end | ||
| 267 | local log = " --rejected-log=results/wget-%s_REJECTS-" .. host .. IP .. ".log -a results/wget-%s-" .. host .. IP ..".log " | ||
| 268 | I("starting file download commands for " .. host .. " " .. URL) | ||
| 269 | local cm = "ionice -c3 " .. download .. log:format("debs", "debs") | ||
| 270 | for i, s in pairs(referenceDebs) do | ||
| 271 | cm = cm .. " https://" .. host .. URL .. "/" .. s | ||
| 272 | end | ||
| 273 | for i, s in pairs(releases) do | ||
| 274 | execute(cm .. " &") | ||
| 275 | cm = "ionice -c3 " .. download .. log:format(s, s) | ||
| 276 | for j, k in pairs(releaseFiles) do | ||
| 277 | cm = cm .. " https://" .. host .. URL .. "/merged/dists/" .. s .. k | ||
| 278 | end | ||
| 279 | end | ||
| 280 | execute(cm .. " &") | ||
| 281 | end | ||
| 282 | |||
| 283 | local getMirrors = function () | ||
| 284 | local mirrors = {} | ||
| 285 | local host = "" | ||
| 286 | local m = {} | ||
| 287 | local URL = "https://" .. options.referenceSite.value .. "/mirror_list.txt" | ||
| 288 | I("getting mirrors.") | ||
| 289 | local p, c, h = http.request(URL) | ||
| 290 | if nil == p then E(c .. " fetching " .. URL) else | ||
| 291 | for l in p:gmatch("\n*([^\n]+)\n*") do | ||
| 292 | local t, d = l:match("(%a*):%s*(.*)") | ||
| 293 | if "FQDN" == t then | ||
| 294 | if "" ~= host then | ||
| 295 | mirrors[host] = m | ||
| 296 | m = {} | ||
| 297 | end | ||
| 298 | host = d | ||
| 299 | end | ||
| 300 | m[t] = d | ||
| 301 | end | ||
| 302 | if "" ~= host then | ||
| 303 | mirrors[host] = m | ||
| 304 | end | ||
| 305 | end | ||
| 306 | return mirrors | ||
| 307 | end | ||
| 308 | |||
| 309 | |||
| 310 | if 0 ~= #args then | ||
| 311 | local option = "" | ||
| 312 | for i, a in pairs(args) do | ||
| 313 | if ("--help" == a) or ("-h" == a) then | ||
| 314 | print("I should write some docs, huh?") | ||
| 315 | elseif "--version" == a then | ||
| 316 | print("mirror-checker-lua version 0.1 alpha") | ||
| 317 | elseif "-v" == a then | ||
| 318 | verbosity = verbosity + 1 | ||
| 319 | sendArgs = sendArgs .. a .. " " | ||
| 320 | elseif "-q" == a then | ||
| 321 | verbosity = -1 | ||
| 322 | sendArgs = sendArgs .. a .. " " | ||
| 323 | elseif "-k" == a then | ||
| 324 | keep = true | ||
| 325 | elseif "--" == a:sub(1, 2) then | ||
| 326 | local s, e = a:find("=") | ||
| 327 | if nil == s then e = -1 end | ||
| 328 | option = a:sub(3, e - 1) | ||
| 329 | local o = options[option] | ||
| 330 | if nil == o then | ||
| 331 | print("Unknown option --" .. option) | ||
| 332 | option = "" | ||
| 333 | else | ||
| 334 | option = a | ||
| 335 | sendArgs = sendArgs .. a .. " " | ||
| 336 | local s, e = a:find("=") | ||
| 337 | if nil == s then e = 0 end | ||
| 338 | option = a:sub(3, e - 1) | ||
| 339 | if "table" == options[option].typ then | ||
| 340 | local result = {} | ||
| 341 | for t in (a:sub(e + 1) .. ","):gmatch("([+%-]?%w*),") do | ||
| 342 | local f = t:sub(1, 1) | ||
| 343 | local n = t:sub(2, -1) | ||
| 344 | if ("+" ~= f) and ("-" ~= f) then | ||
| 345 | table.insert(result, t) | ||
| 346 | end | ||
| 347 | end | ||
| 348 | if 0 ~= #result then | ||
| 349 | options[option].value = result | ||
| 350 | else | ||
| 351 | for t in (a:sub(e + 1) .. ","):gmatch("([+%-]?%w*),") do | ||
| 352 | local f = t:sub(1, 1) | ||
| 353 | local n = t:sub(2, -1) | ||
| 354 | if "+" == f then | ||
| 355 | table.insert(options[option].value, n) | ||
| 356 | elseif "-" == f then | ||
| 357 | local r = {} | ||
| 358 | for i, k in pairs(options[option].value) do | ||
| 359 | if k ~= n then table.insert(r, k) end | ||
| 360 | end | ||
| 361 | options[option].value = r | ||
| 362 | end | ||
| 363 | end | ||
| 364 | end | ||
| 365 | else | ||
| 366 | options[option].value = a | ||
| 367 | end | ||
| 368 | option = "" | ||
| 369 | end | ||
| 370 | elseif "-" == a:sub(1, 1) then | ||
| 371 | print("Unknown option " .. a) | ||
| 372 | else | ||
| 373 | table.insert(arg, a) | ||
| 374 | end | ||
| 375 | end | ||
| 376 | end | ||
| 377 | |||
| 378 | --printTable(options.tests.value, "", "tests") | ||
| 379 | |||
| 380 | execute("mkdir -p results") | ||
| 381 | |||
| 382 | if 0 < #arg then | ||
| 383 | if nil ~= arg[2] | ||
| 384 | log = io.open ("mirror-checker-lua_" .. arg[1] .. "_" .. arg[2] .. ".log", "a+") | ||
| 385 | else | ||
| 386 | log = io.open ("mirror-checker-lua_" .. arg[1] .. ".log", "a+" [, mode]) | ||
| 387 | end | ||
| 388 | local pu = url.parse("http://" .. arg[1], defaultURL) | ||
| 389 | I("Starting tests for " ..pu.host .. " with these tests - " .. table.concat(options.tests.value, ", ")) | ||
| 390 | if nil ~= arg[2] then I(" Using IP " .. arg[2]) end | ||
| 391 | if testing("Integrity") or testing("Updated") then | ||
| 392 | if not keep then execute("rm -fr results/" .. pu.host) end | ||
| 393 | downloads(pu.host, pu.path, arg[2]) | ||
| 394 | checkExes("mirror-checker.lua " .. sendArgs) | ||
| 395 | checkExes(download) | ||
| 396 | end | ||
| 397 | checkHost(pu.host, pu.path, arg[2]) | ||
| 398 | else | ||
| 399 | if not keep then os.execute("rm -f results/*.log") end | ||
| 400 | log = io.open ("mirror-checker-lua.log", "a+") | ||
| 401 | I("Starting tests " .. table.concat(options.tests.value, ", ")) | ||
| 402 | execute("mkdir -p results") | ||
| 403 | local mirrors = getMirrors() | ||
| 404 | mirrors[options.referenceSite.value] = nil | ||
| 405 | -- checkHost(options.referenceSite.value) | ||
| 406 | forkIP(options.referenceSite.value) | ||
| 407 | -- checkHost("deb.devuan.org") | ||
| 408 | forkIP("deb.devuan.org") | ||
| 409 | for k, m in pairs(mirrors) do | ||
| 410 | local pu = url.parse("http://" .. m.BaseURL, defaultURL) | ||
| 411 | -- checkHost(pu.host) | ||
| 412 | forkIP(m.BaseURL) | ||
| 413 | checkExes("mirror-checker.lua " .. sendArgs) | ||
| 414 | if testing("Integrity") or testing("Updated") then checkExes(download) end | ||
| 415 | end | ||
| 416 | while 1 <= checkExes("mirror-checker.lua " .. sendArgs) do os.execute("sleep 30") end | ||
| 417 | if testing("Integrity") or testing("Updated") then | ||
| 418 | while 1 < checkExes(download) do os.execute("sleep 30") end | ||
| 419 | end | ||
| 420 | end | ||
