Credo: the 7777 scanner split up, ip neighbor through System.cmd

bf3c607 · Gabe Morell · 2026-11-04 20:45

5 files +144 -144

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