Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,25 @@ jobs:
if: runner.os == 'macOS'
run: sudo ifconfig lo0 alias 127.0.0.2

- name: Create isolated BPF test interfaces
if: runner.os == 'macOS'
run: |
sudo ifconfig feth0 create
sudo ifconfig feth1 create
sudo ifconfig feth1 peer feth0
sudo ifconfig feth0 up
sudo ifconfig feth1 up
# Only this ephemeral runner gets direct BPF access. No physical NIC,
# IP address, bridge, or Internet sharing is used by these tests.
sudo chown "$(id -u):$(id -g)" /dev/bpf*
echo "ETHERCAT_BPF_INTERFACE=feth0" >> "$GITHUB_ENV"
echo "ETHERCAT_BPF_PEER_INTERFACE=feth1" >> "$GITHUB_ENV"

- name: Run CI
run: mix ci

- name: Remove BPF test interfaces
if: always() && runner.os == 'macOS'
run: |
sudo ifconfig feth0 destroy
sudo ifconfig feth1 destroy
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Raw Ethernet on macOS: `{:raw, %{interface: "en7"}}` uses the new
`EtherCAT.Bus.Transport.Bpf`, which sends and receives EtherCAT frames through
a BPF device with a small C port program (`priv/bpf/ethercat_bpf.c`), compiled
with the system's C compiler on first use. `EtherCAT.Bus.Transport.raw/0`
gives the raw transport of the operating system. Linux is unchanged (`1be936c`).
Helper builds use owner-only temporary directories and a VM-local cache rather
than trusting pre-existing executables; helper exits are reported as closed
transports (`d8d59e3`).

### Fixed
- CoE mailbox response polling uses a one-second monotonic deadline rather than
a poll count, preventing scheduler and bus delays from prolonging polling or
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ Restart IEx/the application after granting the capability. Replace `eth0` with
your interface. Scan **before** opening a master session; scanning assigns
station addresses and must not run against an active ring.

On macOS, for a rack on a desk, the same `{:raw, %{interface: "en7"}}` backend
works through a BPF device and a small C port program, which is compiled with
the system's C compiler the first time it is used. It needs read and write
access to `/dev/bpf*`: root, or the `access_bpf` group that Wireshark's ChmodBPF
sets up. See `EtherCAT.Bus.Transport.Bpf`. The raw simulator and redundant
rings are Linux only.

```elixir
backend = {:raw, %{interface: "eth0"}}
{:ok, scan} = EtherCAT.Scan.scan(backend)
Expand Down Expand Up @@ -193,6 +200,29 @@ The driver comparison demonstrates a shared signal catalogue without changing
the public callbacks. The command above explicitly excludes raw transports.
Hardware and raw-socket tests need separate setup.

### macOS BPF tests without hardware

With the Xcode command line tools and read/write access to `/dev/bpf*` (see
`EtherCAT.Bus.Transport.Bpf`), create an isolated virtual Ethernet pair:

```bash
sudo ifconfig feth0 create
sudo ifconfig feth1 create
sudo ifconfig feth1 peer feth0
sudo ifconfig feth0 up
sudo ifconfig feth1 up
ETHERCAT_BPF_INTERFACE=feth0 ETHERCAT_BPF_PEER_INTERFACE=feth1 mix ci
sudo ifconfig feth0 destroy
sudo ifconfig feth1 destroy
```

Do not attach these interfaces to a bridge or use physical adapters. These tests
send raw frames and check actual receive filtering, VLANs, padding, no self-receive,
and helper lifecycle. CI provisions this pair automatically and runs the same
`mix ci` on Linux and macOS. Without the BPF environment variables, wire tests are
excluded; once configured, setup or helper errors fail the suite. Portable helper
build-security and port-liveness tests run on both platforms.

## Pick your next task

- **Write a driver:** [callback contract and catalogue pattern](https://hexdocs.pm/ethercat/0.5.0/EtherCAT.Driver.html).
Expand Down
4 changes: 2 additions & 2 deletions lib/ethercat/bus/link/redundant.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ defmodule EtherCAT.Bus.Link.Redundant do
alias EtherCAT.Bus.Frame
alias EtherCAT.Bus.Link
alias EtherCAT.Bus.Link.{RedundantMerge, Submission}
alias EtherCAT.Bus.Transport.RawSocket
alias EtherCAT.Bus.Transport
alias EtherCAT.Telemetry

@max_dispatch_errors 3
Expand Down Expand Up @@ -1086,6 +1086,6 @@ defmodule EtherCAT.Bus.Link.Redundant do
end

defp resolve_transport_mod(opts) do
opts[:transport_mod] || RawSocket
opts[:transport_mod] || Transport.raw()
end
end
5 changes: 3 additions & 2 deletions lib/ethercat/bus/link/single.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ defmodule EtherCAT.Bus.Link.Single do
alias EtherCAT.Bus.{Datagram, Frame}
alias EtherCAT.Bus.Link
alias EtherCAT.Bus.Link.Submission
alias EtherCAT.Bus.Transport.{RawSocket, UdpSocket}
alias EtherCAT.Bus.Transport
alias EtherCAT.Bus.Transport.UdpSocket
alias EtherCAT.Telemetry

@max_dispatch_errors 3
Expand Down Expand Up @@ -476,7 +477,7 @@ defmodule EtherCAT.Bus.Link.Single do
opts[:transport_mod] ||
case opts[:transport] do
:udp -> UdpSocket
_ -> RawSocket
_ -> Transport.raw()
end
end
end
15 changes: 14 additions & 1 deletion lib/ethercat/bus/transport.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ defmodule EtherCAT.Bus.Transport do

## Implementations

- `EtherCAT.Bus.Transport.RawSocket` — AF_PACKET raw Ethernet
- `EtherCAT.Bus.Transport.RawSocket` — AF_PACKET raw Ethernet, on Linux
- `EtherCAT.Bus.Transport.Bpf` — BPF raw Ethernet through a port program, on macOS
- `EtherCAT.Bus.Transport.UdpSocket` — UDP/IP encapsulation (spec §2.6)

## Async receive flow
Expand All @@ -25,6 +26,18 @@ defmodule EtherCAT.Bus.Transport do

@type t :: struct()

@doc """
The raw Ethernet transport of this operating system: `EtherCAT.Bus.Transport.Bpf` on macOS,
which has no `AF_PACKET` sockets, and `EtherCAT.Bus.Transport.RawSocket` everywhere else.
"""
@spec raw() :: module()
def raw do
case :os.type() do
{:unix, :darwin} -> EtherCAT.Bus.Transport.Bpf
_other -> EtherCAT.Bus.Transport.RawSocket
end
end

@doc "Open a transport from keyword options."
@callback open(keyword()) :: {:ok, t()} | {:error, term()}

Expand Down
Loading
Loading