func (packet *PPPoEPacket) Validate() (err error) {
	specMap := map[PPPoECode]*packetSpec{
		PPPoECodePADI: &packetSpec{
			zeroSessionID: true,
			mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName},
		},
		PPPoECodePADO: &packetSpec{
			zeroSessionID: true,
			mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName, PPPoETagTypeACName},
		},
		PPPoECodePADR: &packetSpec{
			zeroSessionID: true,
			mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName},
		},
		PPPoECodePADT: &packetSpec{
			zeroSessionID: false,
		},
	}
	var spec *packetSpec
	var tags []*PPPoETag
	var ok bool
	if spec, ok = specMap[packet.Code]; !ok {
		if packet.Code == PPPoECodePADS {
			if packet.SessionID == 0 {
				spec = &packetSpec{
					zeroSessionID: true,
					mandatoryTags: []PPPoETagType{PPPoETagTypeServiceNameError},
				}
			} else {
				spec = &packetSpec{
					zeroSessionID: false,
					mandatoryTags: []PPPoETagType{PPPoETagTypeServiceName},
				}
			}
		} else {
			return fmt.Errorf("unrecognised packet code %v", packet.Code)
		}
	}
	if spec.zeroSessionID {
		if packet.SessionID != 0 {
			return fmt.Errorf("nonzero session ID in %v; must have zero", packet.Code)
		}
	} else {
		if packet.SessionID == 0 {
			return fmt.Errorf("zero session ID in %v; must have nonzero", packet.Code)
		}
	}
	if len(packet.Tags) < len(spec.mandatoryTags) {
		return fmt.Errorf("expect minimum of %d tags in %v; only got %d",
			len(spec.mandatoryTags), packet.Code, len(tags))
	}
	for _, tagType := range spec.mandatoryTags {
		_, err := findTag(tagType, packet.Tags)
		if err != nil {
			return fmt.Errorf("missing mandatory tag %v in %v", tagType, packet.Code)
		}
	}
	return nil
}
Validate validates a packet meets the requirements of RFC2516, checking
the mandatory tags are included and the session ID is set correctly.
package pppoe
import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
)
type PPPoETag struct {
	Type PPPoETagType
	Data []byte
}
type PPPoEPacket struct {
	SrcHWAddr [6]byte
	DstHWAddr [6]byte
	Code PPPoECode
	SessionID PPPoESessionID
	Tags []*PPPoETag
}
func (code PPPoECode) String() string {
	switch code {
	case PPPoECodePADI:
		return "PADI"
	case PPPoECodePADO:
		return "PADO"
	case PPPoECodePADR:
		return "PADR"
	case PPPoECodePADS:
		return "PADS"
	case PPPoECodePADT:
		return "PADT"
	}
	return "???"
}
func (typ PPPoETagType) String() string {
	switch typ {
	case PPPoETagTypeEOL:
		return "EOL"
	case PPPoETagTypeServiceName:
		return "Service Name"
	case PPPoETagTypeACName:
		return "AC Name"
	case PPPoETagTypeHostUniq:
		return "Host Uniq"
	case PPPoETagTypeACCookie:
		return "AC Cookie"
	case PPPoETagTypeVendorSpecific:
		return "Vendor Specific"
	case PPPoETagTypeRelaySessionID:
		return "Relay Session ID"
	case PPPoETagTypeServiceNameError:
		return "Service Name Error"
	case PPPoETagTypeACSystemError:
		return "AC System Error"
	case PPPoETagTypeGenericError:
		return "Generic Error"
	default:
		return "Unknown"
	}
}
func (tag *PPPoETag) String() string {
	switch tag.Type {
	case PPPoETagTypeServiceName,
		PPPoETagTypeACName,
		PPPoETagTypeServiceNameError,
		PPPoETagTypeACSystemError,
		PPPoETagTypeGenericError:
		return fmt.Sprintf("%v: '%s'", tag.Type, string(tag.Data))
	}
	return fmt.Sprintf("%v: %#v", tag.Type, tag.Data)
}
func (packet *PPPoEPacket) String() string {
	s := fmt.Sprintf("%s: src %s, dst %s, session %v, tags:",
		packet.Code,
		fmt.Sprintf("0x%02x:%02x:%02x:%02x:%02x:%02x",
			packet.SrcHWAddr[0],
			packet.SrcHWAddr[1],
			packet.SrcHWAddr[2],
			packet.SrcHWAddr[3],
			packet.SrcHWAddr[4],
			packet.SrcHWAddr[5]),
		fmt.Sprintf("0x%02x:%02x:%02x:%02x:%02x:%02x",
			packet.DstHWAddr[0],
			packet.DstHWAddr[1],
			packet.DstHWAddr[2],
			packet.DstHWAddr[3],
			packet.DstHWAddr[4],
			packet.DstHWAddr[5]),
		packet.SessionID)
	for _, tag := range packet.Tags {
		s += fmt.Sprintf(" %s,", tag)
	}
	return s
}
func ethTypeDiscovery() uint16 {
	return 0x8863
}
func ethTypeDiscoveryNetBytes() []byte {
	ethType := make([]byte, 2)
	binary.BigEndian.PutUint16(ethType, ethTypeDiscovery())
	return ethType
}
func ethTypeDiscoveryNetUint16() uint16 {
	b := ethTypeDiscoveryNetBytes()
	return uint16(b[1])<<8 + uint16(b[0])
}
func NewPADI(sourceHWAddr [6]byte, serviceName string) (packet *PPPoEPacket, err error) {
	packet = &PPPoEPacket{
		SrcHWAddr: sourceHWAddr,
		DstHWAddr: [6]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
		Code:      PPPoECodePADI,
		SessionID: 0,
	}
	err = packet.AddServiceNameTag(serviceName)
	if err != nil {
		return nil, err
	}
	return
}
func NewPADO(sourceHWAddr [6]byte, destHWAddr [6]byte, serviceName string, acName string) (packet *PPPoEPacket, err error) {
	packet = &PPPoEPacket{
		SrcHWAddr: sourceHWAddr,
		DstHWAddr: destHWAddr,
		Code:      PPPoECodePADO,
		SessionID: 0,
	}
	err = packet.AddServiceNameTag(serviceName)
	if err != nil {
		return nil, err
	}
	err = packet.AddACNameTag(acName)
	if err != nil {
		return nil, err
	}
	return
}
func NewPADR(sourceHWAddr [6]byte, destHWAddr [6]byte, serviceName string) (packet *PPPoEPacket, err error) {
	packet = &PPPoEPacket{
		SrcHWAddr: sourceHWAddr,
		DstHWAddr: destHWAddr,
		Code:      PPPoECodePADR,
		SessionID: 0,
	}
	err = packet.AddServiceNameTag(serviceName)
	if err != nil {
		return nil, err
	}
	return
}
func NewPADS(sourceHWAddr [6]byte, destHWAddr [6]byte, serviceName string, sid PPPoESessionID) (packet *PPPoEPacket, err error) {
	packet = &PPPoEPacket{
		SrcHWAddr: sourceHWAddr,
		DstHWAddr: destHWAddr,
		Code:      PPPoECodePADS,
		SessionID: sid,
	}
	err = packet.AddServiceNameTag(serviceName)
	if err != nil {
		return nil, err
	}
	return
}
func NewPADT(sourceHWAddr [6]byte, destHWAddr [6]byte, sid PPPoESessionID) (packet *PPPoEPacket, err error) {
	return &PPPoEPacket{
		SrcHWAddr: sourceHWAddr,
		DstHWAddr: destHWAddr,
		Code:      PPPoECodePADT,
		SessionID: sid,
	}, nil
}
type pppoeHeader struct {
	DstHWAddr [6]byte
	SrcHWAddr [6]byte
	EtherType uint16
	VerType   uint8
	Code      uint8
	SessionID uint16
	Length    uint16
}
func findTag(typ PPPoETagType, tags []*PPPoETag) (tag *PPPoETag, err error) {
	for _, tag = range tags {
		if tag.Type == typ {
			return tag, nil
		}
	}
	return nil, fmt.Errorf("no tag %v found", typ)
}
type packetSpec struct {
	zeroSessionID bool
	mandatoryTags []PPPoETagType
}