neiam
/lambent_ex
Lambent Aether 5
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
Credo: the 7777 scanner split up, ip neighbor through System.cmd
bf3c607 · Gabe Morell · 2026-11-04 20:45
Files changed
modified
lib/lambent_ex/scan/8266-7777.ex
+130
−133
@@ -201,30 +201,14 @@ defmodule LambentEx.Scan.ESP8266x7777 do
| 201 | 201 | target = poke_target(mode, device) |
| 202 | 202 | Logger.notice("#{@p} Poking #{dvc} (#{target})") |
| 203 | 203 | |
| 204 | − for {i, c} <- poke_steps(target) do | |
| 205 | − case target do | |
| 206 | − :indicator -> | |
| 207 | − Process.send_after(self(), {:send_ind, device, c}, i) | |
| 208 | − | |
| 209 | − :strands -> | |
| 210 | − if device["sox"] == 1 do | |
| 211 | − Process.send_after(self(), {:send, device, c}, i) | |
| 212 | − else | |
| 213 | − # ranges are inclusive, and the device only binds sox ports | |
| 214 | − 0..(max(device["sox"], 1) - 1) | |
| 215 | − |> Enum.map(fn idx -> | |
| 216 | − Process.send_after(self(), {:send, device, c, idx}, i) | |
| 217 | − end) | |
| 218 | − end | |
| 219 | − end | |
| 220 | − end | |
| 204 | + for {i, c} <- poke_steps(target), do: send_poke(target, device, c, i) | |
| 221 | 205 | |
| 222 | 206 | {:noreply, state} |
| 223 | 207 | end |
| 224 | 208 | end |
| 225 | 209 | |
| 226 | 210 | def handle_cast({:scan_results, iface, ip, count, ind, ver}, state) do |
| 227 | − IO.inspect({"SR", iface, ip, count, ind, ver}) | |
| 211 | + Logger.debug("#{@s} Scan result #{inspect({iface, ip, count, ind, ver})}") | |
| 228 | 212 | if_fmt = Kernel.to_string(iface) |
| 229 | 213 | ip_fmt = ip |> String.split(".") |> Enum.map(&String.to_integer/1) |> List.to_tuple() |
| 230 | 214 | instances = %{if_fmt => [{ip, count, ind, ver}]} |
@@ -364,37 +348,28 @@ defmodule LambentEx.Scan.ESP8266x7777 do
| 364 | 348 | decode_body: false |
| 365 | 349 | ) do |
| 366 | 350 | {:ok, resp} -> |
| 367 | − if ip == "172.16.15.80", do: IO.inspect(resp) | |
| 351 | + classify(resp, ip) | |
| 368 | 352 | |
| 369 | − case resp.status do | |
| 370 | − 404 -> | |
| 371 | − case resp.body do | |
| 372 | − "File not found." -> {:lambent, ip, 0, nil, nil} | |
| 373 | − _other -> {:not_lambent, ip, -1, nil, nil} | |
| 374 | − end | |
| 375 | − | |
| 376 | − # esp32rs | |
| 377 | − 218 -> | |
| 378 | − IO.inspect({"218@", ip}) | |
| 353 | + {:error, _err} -> | |
| 354 | + {:not_lambent, ip, -1, nil, nil} | |
| 355 | + end | |
| 356 | + end | |
| 379 | 357 | |
| 380 | − case resp.body |> Jason.decode(keys: :atoms) do | |
| 381 | − {:ok, json} -> | |
| 382 | − {:lambent, ip, json.udp_sox |> length, indicator_port(json), version(json)} | |
| 358 | + defp classify(%{status: 404, body: "File not found."}, ip), do: {:lambent, ip, 0, nil, nil} | |
| 383 | 359 | |
| 384 | − {:error, _error} -> | |
| 385 | − {:not_lambent, ip, -1, nil, nil} | |
| 386 | − end | |
| 360 | + # esp32rs | |
| 361 | + defp classify(%{status: 218, body: body}, ip) do | |
| 362 | + case body |> Jason.decode(keys: :atoms) do | |
| 363 | + {:ok, json} -> | |
| 364 | + {:lambent, ip, json.udp_sox |> length, indicator_port(json), version(json)} | |
| 387 | 365 | |
| 388 | − _other -> | |
| 389 | − {:not_lambent, ip, -1, nil, nil} | |
| 390 | − end | |
| 391 | − | |
| 392 | − {:error, err} -> | |
| 393 | − if ip == "172.16.15.80", do: IO.inspect(err) | |
| 366 | + {:error, _error} -> | |
| 394 | 367 | {:not_lambent, ip, -1, nil, nil} |
| 395 | 368 | end |
| 396 | 369 | end |
| 397 | 370 | |
| 371 | + defp classify(_resp, ip), do: {:not_lambent, ip, -1, nil, nil} | |
| 372 | + | |
| 398 | 373 | # the firmware only advertises an indicator it actually brought up, and older |
| 399 | 374 | # builds do not advertise one at all |
| 400 | 375 | defp indicator_port(json) do |
@@ -416,36 +391,23 @@ defmodule LambentEx.Scan.ESP8266x7777 do
| 416 | 391 | defp rgb_shift(stream, device) do |
| 417 | 392 | chunked = Enum.chunk_every(stream, 3) |
| 418 | 393 | |
| 419 | − case device |> Map.get("ord") do | |
| 420 | − "rgb" -> | |
| 421 | − chunked |> Enum.map(fn [r, g, b] -> [g, r, b] end) | |
| 422 | − | |
| 423 | − "grb" -> | |
| 424 | − chunked |> Enum.map(fn [r, g, b] -> [r, g, b] end) | |
| 425 | − | |
| 426 | − "rgbww" -> | |
| 427 | − chunked |> Enum.map(fn [r, g, b] -> [g, r, b, Enum.min(C.wrgb([r, g, b]), r)] end) | |
| 428 | − | |
| 429 | − "rgbnw" -> | |
| 430 | − chunked |> Enum.map(fn [r, g, b] -> [g, r, b, C.wrgb([r, g, b])] end) | |
| 431 | − | |
| 432 | − "rgbcw" -> | |
| 433 | − chunked |> Enum.map(fn [r, g, b] -> [g, r, b, Enum.min([C.wrgb([r, g, b]), b])] end) | |
| 434 | − | |
| 435 | − "rgbaw" -> | |
| 436 | − chunked |> Enum.map(fn [r, g, b] -> [g, r, b, C.argb([r, g, b]), C.wrgb([r, g, b])] end) | |
| 394 | + chunked | |
| 395 | + |> Enum.map(device |> Map.get("ord") |> pixel_order()) | |
| 396 | + |> List.flatten() | |
| 397 | + end | |
| 437 | 398 | |
| 438 | − "rgbxw" -> | |
| 439 | − chunked |> Enum.map(fn [r, g, b] -> [g, r, b, 0] end) | |
| 399 | + defp pixel_order("rgb"), do: fn [r, g, b] -> [g, r, b] end | |
| 400 | + defp pixel_order("grb"), do: fn [r, g, b] -> [r, g, b] end | |
| 401 | + defp pixel_order("rgbww"), do: fn [r, g, b] -> [g, r, b, Enum.min(C.wrgb([r, g, b]), r)] end | |
| 402 | + defp pixel_order("rgbnw"), do: fn [r, g, b] -> [g, r, b, C.wrgb([r, g, b])] end | |
| 403 | + defp pixel_order("rgbcw"), do: fn [r, g, b] -> [g, r, b, Enum.min([C.wrgb([r, g, b]), b])] end | |
| 440 | 404 | |
| 441 | − "grbxw" -> | |
| 442 | − chunked |> Enum.map(fn [r, g, b] -> [r, g, b, 0] end) | |
| 405 | + defp pixel_order("rgbaw"), | |
| 406 | + do: fn [r, g, b] -> [g, r, b, C.argb([r, g, b]), C.wrgb([r, g, b])] end | |
| 443 | 407 | |
| 444 | − nil -> | |
| 445 | − chunked |> Enum.map(fn [r, g, b] -> [g, r, b] end) | |
| 446 | − end | |
| 447 | − |> List.flatten() | |
| 448 | − end | |
| 408 | + defp pixel_order("rgbxw"), do: fn [r, g, b] -> [g, r, b, 0] end | |
| 409 | + defp pixel_order("grbxw"), do: fn [r, g, b] -> [r, g, b, 0] end | |
| 410 | + defp pixel_order(nil), do: fn [r, g, b] -> [g, r, b] end | |
| 449 | 411 | |
| 450 | 412 | defp rgb_stream(stream) do |
| 451 | 413 | # TOOD FIGURE OUT HOW TO RECREATE THE PYTHON PACKING |
@@ -577,8 +539,7 @@ defmodule LambentEx.Scan.ESP8266x7777 do
| 577 | 539 | curr = state[:arps] |
| 578 | 540 | |
| 579 | 541 | neighbors = |
| 580 | − :os.cmd(~c"ip neighbor show") | |
| 581 | − |> Kernel.to_string() | |
| 542 | + ip_neighbors() | |
| 582 | 543 | |> String.split("\n") |
| 583 | 544 | |> Enum.filter(fn x -> |
| 584 | 545 | case x |> String.split(" ", trim: true) do |
@@ -654,58 +615,7 @@ defmodule LambentEx.Scan.ESP8266x7777 do
| 654 | 615 | |
| 655 | 616 | instances = |
| 656 | 617 | LambentEx.Scan.NMAP.retrieve() |
| 657 | − |> Enum.map(fn {i, results} -> | |
| 658 | − iface_items = | |
| 659 | − state.devices | |
| 660 | − |> Enum.map(fn {_m, %{"ip" => ip}} -> ip |> Tuple.to_list() |> Enum.join(".") end) | |
| 661 | − | |
| 662 | − keys = | |
| 663 | − results | |
| 664 | − |> Map.keys() | |
| 665 | − |> Enum.filter(fn k -> k != "" end) | |
| 666 | − |> Enum.reject(fn x -> Enum.member?(iface_items, x) end) | |
| 667 | − | |
| 668 | − # todo avoid booming by accruing a counter that only scans existing shit every n scan attempts | |
| 669 | − | |
| 670 | − # state.interfaces | |
| 671 | − # |> Enum.map(fn {i, v} -> | |
| 672 | − # addr = v |> Keyword.get(:addr) | |
| 673 | − # nm = v |> Keyword.get(:netmask) |> LambentEx.Utils.Network.get_netmask() | |
| 674 | − # | |
| 675 | − # lambents = case state.subnet_override |> Map.get(i) do | |
| 676 | − # nil -> LambentEx.Utils.Network.get_ip_range(addr, nm) | |
| 677 | − # val -> val | |
| 678 | − # end | |
| 679 | − # |> IO.inspect | |
| 680 | − # |> trim_range | |
| 681 | − # lambents = | |
| 682 | − # keys | |
| 683 | − # |> IO.inspect() | |
| 684 | − # |> Enum.map(&Task.async(fn -> query_http(&1) end)) | |
| 685 | − # |> Enum.map(fn x -> Task.await(x, 30000) end) | |
| 686 | − # |> Enum.filter(fn {return, _value, _ch} -> return == :lambent end) | |
| 687 | − # |> Enum.map(fn {_atom, ip, sox} -> {ip, sox} end) | |
| 688 | − | |
| 689 | − lambents = | |
| 690 | − keys | |
| 691 | − |> IO.inspect() | |
| 692 | − |> Enum.map(fn ip -> | |
| 693 | − Task.Supervisor.async_nolink(LambentEx.Scan.Connectivity.Supervisor, fn -> | |
| 694 | − case query_http(ip) do | |
| 695 | − {:lambent, ip, cnt, ind, ver} -> | |
| 696 | − via_tuple("scan_8266") | |
| 697 | − |> GenServer.cast({:scan_results, i, ip, cnt, ind, ver}) | |
| 698 | − | |
| 699 | − _otherwise -> | |
| 700 | − :ok | |
| 701 | − end | |
| 702 | − end) | |
| 703 | − end) | |
| 704 | − | |
| 705 | − # Logger.notice("#{@s} Found #{lambents |> Kernel.length()} on #{i}") | |
| 706 | − # IO.inspect(lambents) | |
| 707 | − # {i |> Kernel.to_string(), lambents} | |
| 708 | − end) | |
| 618 | + |> Enum.map(&scan_iface(&1, state)) | |
| 709 | 619 | |
| 710 | 620 | # |> Map.new() |
| 711 | 621 | # IO.inspect(instances) |
@@ -767,6 +677,90 @@ defmodule LambentEx.Scan.ESP8266x7777 do
| 767 | 677 | {:noreply, state} |
| 768 | 678 | end |
| 769 | 679 | |
| 680 | + defp send_poke(:indicator, device, c, i), | |
| 681 | + do: Process.send_after(self(), {:send_ind, device, c}, i) | |
| 682 | + | |
| 683 | + defp send_poke(:strands, device, c, i) do | |
| 684 | + if device["sox"] == 1 do | |
| 685 | + Process.send_after(self(), {:send, device, c}, i) | |
| 686 | + else | |
| 687 | + # ranges are inclusive, and the device only binds sox ports | |
| 688 | + 0..(max(device["sox"], 1) - 1) | |
| 689 | + |> Enum.each(fn idx -> | |
| 690 | + Process.send_after(self(), {:send, device, c, idx}, i) | |
| 691 | + end) | |
| 692 | + end | |
| 693 | + end | |
| 694 | + | |
| 695 | + # what `ip neighbor show` prints, or nothing when there is no `ip` to ask | |
| 696 | + defp ip_neighbors do | |
| 697 | + case System.find_executable("ip") do | |
| 698 | + nil -> | |
| 699 | + "" | |
| 700 | + | |
| 701 | + ip -> | |
| 702 | + {out, _status} = System.cmd(ip, ["neighbor", "show"]) | |
| 703 | + out | |
| 704 | + end | |
| 705 | + end | |
| 706 | + | |
| 707 | + defp scan_iface({i, results}, state) do | |
| 708 | + iface_items = | |
| 709 | + state.devices | |
| 710 | + |> Enum.map(fn {_m, %{"ip" => ip}} -> ip |> Tuple.to_list() |> Enum.join(".") end) | |
| 711 | + | |
| 712 | + keys = | |
| 713 | + results | |
| 714 | + |> Map.keys() | |
| 715 | + |> Enum.filter(fn k -> k != "" end) | |
| 716 | + |> Enum.reject(fn x -> Enum.member?(iface_items, x) end) | |
| 717 | + | |
| 718 | + # not done yet: avoid booming by accruing a counter that only scans existing shit every n scan attempts | |
| 719 | + | |
| 720 | + # state.interfaces | |
| 721 | + # |> Enum.map(fn {i, v} -> | |
| 722 | + # addr = v |> Keyword.get(:addr) | |
| 723 | + # nm = v |> Keyword.get(:netmask) |> LambentEx.Utils.Network.get_netmask() | |
| 724 | + # | |
| 725 | + # lambents = case state.subnet_override |> Map.get(i) do | |
| 726 | + # nil -> LambentEx.Utils.Network.get_ip_range(addr, nm) | |
| 727 | + # val -> val | |
| 728 | + # end | |
| 729 | + # |> IO.inspect | |
| 730 | + # |> trim_range | |
| 731 | + # lambents = | |
| 732 | + # keys | |
| 733 | + # |> IO.inspect() | |
| 734 | + # |> Enum.map(&Task.async(fn -> query_http(&1) end)) | |
| 735 | + # |> Enum.map(fn x -> Task.await(x, 30000) end) | |
| 736 | + # |> Enum.filter(fn {return, _value, _ch} -> return == :lambent end) | |
| 737 | + # |> Enum.map(fn {_atom, ip, sox} -> {ip, sox} end) | |
| 738 | + | |
| 739 | + lambents = | |
| 740 | + keys | |
| 741 | + |> tap(&Logger.debug("#{@s} Probing #{inspect(&1)} on #{i}")) | |
| 742 | + |> Enum.map(fn ip -> | |
| 743 | + Task.Supervisor.async_nolink(LambentEx.Scan.Connectivity.Supervisor, fn -> | |
| 744 | + probe(i, ip) | |
| 745 | + end) | |
| 746 | + end) | |
| 747 | + | |
| 748 | + # Logger.notice("#{@s} Found #{lambents |> Kernel.length()} on #{i}") | |
| 749 | + # IO.inspect(lambents) | |
| 750 | + # {i |> Kernel.to_string(), lambents} | |
| 751 | + end | |
| 752 | + | |
| 753 | + defp probe(iface, ip) do | |
| 754 | + case query_http(ip) do | |
| 755 | + {:lambent, ip, cnt, ind, ver} -> | |
| 756 | + via_tuple("scan_8266") | |
| 757 | + |> GenServer.cast({:scan_results, iface, ip, cnt, ind, ver}) | |
| 758 | + | |
| 759 | + _otherwise -> | |
| 760 | + :ok | |
| 761 | + end | |
| 762 | + end | |
| 763 | + | |
| 770 | 764 | defp join_arps(new, state) do |
| 771 | 765 | arps = state[:arps] |
| 772 | 766 |
@@ -825,17 +819,20 @@ defmodule LambentEx.Scan.ESP8266x7777 do
| 825 | 819 | {:ok, addrs} = :inet.getifaddrs() |
| 826 | 820 | |
| 827 | 821 | addrs |
| 828 | − |> Enum.filter(fn x -> | |
| 829 | − case Kernel.elem(x, 1) |> Keyword.get(:addr) do | |
| 830 | − nil -> false | |
| 831 | − addr -> Kernel.tuple_size(addr) == 4 | |
| 832 | − end | |
| 833 | − end) | |
| 834 | − |> Enum.filter(fn x -> | |
| 835 | − !Enum.any?(@filtered_prefixes, fn y -> | |
| 836 | − String.starts_with?(Kernel.elem(x, 0) |> Kernel.to_string(), y) | |
| 837 | − end) | |
| 838 | − end) | |
| 822 | + |> Enum.filter(fn x -> ipv4_iface?(x) and not filtered_iface?(x) end) | |
| 839 | 823 | |> Map.new() |
| 840 | 824 | end |
| 825 | + | |
| 826 | + defp ipv4_iface?(x) do | |
| 827 | + case Kernel.elem(x, 1) |> Keyword.get(:addr) do | |
| 828 | + nil -> false | |
| 829 | + addr -> Kernel.tuple_size(addr) == 4 | |
| 830 | + end | |
| 831 | + end | |
| 832 | + | |
| 833 | + defp filtered_iface?(x) do | |
| 834 | + Enum.any?(@filtered_prefixes, fn y -> | |
| 835 | + String.starts_with?(Kernel.elem(x, 0) |> Kernel.to_string(), y) | |
| 836 | + end) | |
| 837 | + end | |
| 841 | 838 | end |
modified
lib/lambent_ex/scan/nmap.ex
+14
−11
@@ -278,17 +278,20 @@ defmodule LambentEx.Scan.NMAP do
| 278 | 278 | {:ok, addrs} = :inet.getifaddrs() |
| 279 | 279 | |
| 280 | 280 | addrs |
| 281 | − |> Enum.filter(fn x -> | |
| 282 | − case Kernel.elem(x, 1) |> Keyword.get(:addr) do | |
| 283 | − nil -> false | |
| 284 | − addr -> Kernel.tuple_size(addr) == 4 | |
| 285 | − end | |
| 286 | − end) | |
| 287 | − |> Enum.filter(fn x -> | |
| 288 | − !Enum.any?(@filtered_prefixes, fn y -> | |
| 289 | − String.starts_with?(Kernel.elem(x, 0) |> Kernel.to_string(), y) | |
| 290 | − end) | |
| 291 | − end) | |
| 281 | + |> Enum.filter(fn x -> ipv4_iface?(x) and not filtered_iface?(x) end) | |
| 292 | 282 | |> Map.new() |
| 293 | 283 | end |
| 284 | + | |
| 285 | + defp ipv4_iface?(x) do | |
| 286 | + case Kernel.elem(x, 1) |> Keyword.get(:addr) do | |
| 287 | + nil -> false | |
| 288 | + addr -> Kernel.tuple_size(addr) == 4 | |
| 289 | + end | |
| 290 | + end | |
| 291 | + | |
| 292 | + defp filtered_iface?(x) do | |
| 293 | + Enum.any?(@filtered_prefixes, fn y -> | |
| 294 | + String.starts_with?(Kernel.elem(x, 0) |> Kernel.to_string(), y) | |
| 295 | + end) | |
| 296 | + end | |
| 294 | 297 | end |
Parents: 9675d32