func CopyFrames(a, b DuplexEthernetStream) error {
	var wg sync.WaitGroup
	wg.Add(2)
	var err1, err2 error
	go func() {
		err1 = copyLoop(a, b)
		wg.Done()
	}()
	go func() {
		err2 = copyLoop(b, a)
		wg.Done()
	}()
	wg.Wait()
	if err1 != nil {
		return err1
	}
	if err2 != nil {
		return err2
	}
	return nil
}
CopyFrames starts a background process that copies packets between the
given two streams.
package phys
import (
	"context"
	"fmt"
	"io"
	"net"
	"sync"
	"github.com/fragglet/ipxbox/ipx"
	"github.com/fragglet/ipxbox/network/pipe"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
)
const (
	numBufferedPackets = 8
)
var (
	_ = (ipx.ReadWriteCloser)(&Phys{})
)
type DuplexEthernetStream interface {
	gopacket.PacketDataSource
	Close()
	WritePacketData([]byte) error
}
type Phys struct {
	stream DuplexEthernetStream
	ps     *gopacket.PacketSource
	rxpipe ipx.ReadWriteCloser
	framer Framer
	nonIPX *nonIPX
	mu     sync.Mutex
}
func (p *Phys) Close() error {
	p.rxpipe.Close()
	p.mu.Lock()
	if p.nonIPX != nil {
		p.nonIPX.Close()
	}
	p.mu.Unlock()
	p.stream.Close()
	return nil
}
func (p *Phys) Run() error {
	for {
		pkt, err := p.ps.NextPacket()
		if err != nil {
			return err
		}
		payload, ok := GetIPXPayload(pkt)
		if ok {
			ipxpkt := &ipx.Packet{}
			if err := ipxpkt.UnmarshalBinary(payload); err != nil {
				return err
			}
			p.rxpipe.WritePacket(ipxpkt)
		} else {
			p.mu.Lock()
			if p.nonIPX != nil {
				p.nonIPX.frames <- pkt
			}
			p.mu.Unlock()
		}
	}
}
func (p *Phys) ReadPacket(ctx context.Context) (*ipx.Packet, error) {
	return p.rxpipe.ReadPacket(ctx)
}
func (p *Phys) WritePacket(packet *ipx.Packet) error {
	dest := net.HardwareAddr(packet.Header.Dest.Addr[:])
	buf := gopacket.NewSerializeBuffer()
	opts := gopacket.SerializeOptions{}
	layers, err := p.framer.Frame(dest, packet)
	if err != nil {
		return err
	}
	gopacket.SerializeLayers(buf, opts, layers...)
	if err := p.stream.WritePacketData(buf.Bytes()); err != nil {
		return err
	}
	return nil
}
func (p *Phys) NonIPX() DuplexEthernetStream {
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.nonIPX == nil {
		p.nonIPX = &nonIPX{
			phys:   p,
			frames: make(chan gopacket.Packet),
			sb:     gopacket.NewSerializeBuffer(),
		}
	}
	return p.nonIPX
}
type nonIPX struct {
	phys   *Phys
	frames chan gopacket.Packet
	sb     gopacket.SerializeBuffer
}
func (ni *nonIPX) serializePacket(pkt gopacket.Packet) ([]byte, error) {
	ls := pkt.Layers()
	eth, ok := ls[0].(*layers.Ethernet)
	if !ok {
		return nil, fmt.Errorf("non-ethernet frame (this should not happen")
	}
	newLayers := []gopacket.SerializableLayer{eth}
	if ip, ok := ls[1].(*layers.IPv4); ok {
		newLayers = append(newLayers, ip)
		if udp, ok := ls[2].(*layers.UDP); ok {
			newLayers = append(newLayers, udp)
			udp.SetNetworkLayerForChecksum(ip)
		} else if tcp, ok := ls[2].(*layers.TCP); ok {
			newLayers = append(newLayers, tcp)
			tcp.SetNetworkLayerForChecksum(ip)
		}
	}
	payload := newLayers[len(newLayers)-1].(gopacket.Layer).LayerPayload()
	newLayers = append(newLayers, gopacket.Payload(payload))
	opts := gopacket.SerializeOptions{
		ComputeChecksums: true,
	}
	if err := gopacket.SerializeLayers(ni.sb, opts, newLayers...); err != nil {
		return nil, err
	}
	return ni.sb.Bytes(), nil
}
func (ni *nonIPX) ReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
	pkt, ok := <-ni.frames
	if !ok {
		return nil, gopacket.CaptureInfo{}, io.EOF
	}
	result, err := ni.serializePacket(pkt)
	if err != nil {
		return nil, gopacket.CaptureInfo{}, err
	}
	return result, pkt.Metadata().CaptureInfo, nil
}
func (ni *nonIPX) WritePacketData(frame []byte) error {
	return ni.phys.stream.WritePacketData(frame)
}
func (ni *nonIPX) Close() {
	ni.phys.mu.Lock()
	close(ni.frames)
	ni.phys.nonIPX = nil
	ni.phys.mu.Unlock()
}
func NewPhys(stream DuplexEthernetStream, framer Framer) *Phys {
	return &Phys{
		stream: stream,
		ps:     gopacket.NewPacketSource(stream, layers.LinkTypeEthernet),
		framer: framer,
		rxpipe: pipe.New(numBufferedPackets),
	}
}
func copyLoop(a, b DuplexEthernetStream) error {
	for {
		frame, _, err := a.ReadPacketData()
		switch {
		case err == io.EOF:
			return nil
		case err != nil:
			return err
		}
		if err := b.WritePacketData(frame); err != nil {
			return err
		}
	}
}