note that if you want to play with writing your own network stack on linux, you can use `socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))` and operate on not too dissimilar level of abstraction as this is.
SOCK_RAW packets are passed to and from the device driver without
any changes in the packet data. When receiving a packet, the
address is still parsed and passed in a standard sockaddr_ll
address structure. When transmitting a packet, the user-supplied
buffer should contain the physical-layer header. That packet is
then queued unmodified to the network driver of the interface
defined by the destination address.
And to do it from the other direction, you can open a tun (virtual IP) or tap (virtual Ethernet) interface just as easily. This adds a virtual interface to the network stack (e.g. a VPN interface), while a packet socket does the opposite and lets you communicate directly through with a real interface.