83 lines
2.8 KiB
Go
83 lines
2.8 KiB
Go
package subnet
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
|
|
"remlink/internal/protocol"
|
|
)
|
|
|
|
var (
|
|
ErrUnknownSession = errors.New("unknown or inactive Session")
|
|
ErrOuterSourceMismatch = errors.New("Session outer source does not match peer Overlay IP")
|
|
ErrInnerIPv4Invalid = errors.New("Session payload is not one complete IPv4 packet")
|
|
ErrInnerSourceMismatch = errors.New("Session inner source is not allowed")
|
|
ErrInnerTargetMismatch = errors.New("Session inner destination is not allowed")
|
|
)
|
|
|
|
// ValidateDatagram verifies framing, Active Session identity, outer source,
|
|
// IPv4 total length, and direction-specific inner addresses.
|
|
func ValidateDatagram(data []byte, outerSource netip.Addr, registry *Registry) (uint64, []byte, error) {
|
|
if registry == nil {
|
|
return 0, nil, ErrUnknownSession
|
|
}
|
|
header, packet, err := protocol.DecodeIPv4Session(data)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
binding, found := registry.Lookup(header.SessionID)
|
|
if !found || !binding.Active {
|
|
return 0, nil, ErrUnknownSession
|
|
}
|
|
if outerSource.Unmap() != binding.PeerOverlayIP.Unmap() {
|
|
return 0, nil, ErrOuterSourceMismatch
|
|
}
|
|
source, destination, err := ipv4Addresses(packet)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
switch binding.Direction {
|
|
case EngineerToSite:
|
|
if source != binding.EngineerOverlayIP {
|
|
return 0, nil, fmt.Errorf("%w: got %s want %s", ErrInnerSourceMismatch, source, binding.EngineerOverlayIP)
|
|
}
|
|
if !containedBy(destination, binding.RemoteCIDRs) {
|
|
return 0, nil, fmt.Errorf("%w: %s is outside Remote CIDRs", ErrInnerTargetMismatch, destination)
|
|
}
|
|
case SiteToEngineer:
|
|
if !containedBy(source, binding.RemoteCIDRs) {
|
|
return 0, nil, fmt.Errorf("%w: %s is outside Remote CIDRs", ErrInnerSourceMismatch, source)
|
|
}
|
|
if destination != binding.EngineerOverlayIP {
|
|
return 0, nil, fmt.Errorf("%w: got %s want %s", ErrInnerTargetMismatch, destination, binding.EngineerOverlayIP)
|
|
}
|
|
default:
|
|
return 0, nil, ErrUnknownSession
|
|
}
|
|
return header.SessionID, packet, nil
|
|
}
|
|
|
|
func ipv4Addresses(packet []byte) (source, destination netip.Addr, err error) {
|
|
if len(packet) < 20 || packet[0]>>4 != 4 {
|
|
return netip.Addr{}, netip.Addr{}, ErrInnerIPv4Invalid
|
|
}
|
|
headerLength := int(packet[0]&0x0F) * 4
|
|
totalLength := int(packet[2])<<8 | int(packet[3])
|
|
if headerLength < 20 || headerLength > len(packet) || totalLength != len(packet) || totalLength < headerLength {
|
|
return netip.Addr{}, netip.Addr{}, ErrInnerIPv4Invalid
|
|
}
|
|
source = netip.AddrFrom4([4]byte{packet[12], packet[13], packet[14], packet[15]})
|
|
destination = netip.AddrFrom4([4]byte{packet[16], packet[17], packet[18], packet[19]})
|
|
return source, destination, nil
|
|
}
|
|
|
|
func containedBy(address netip.Addr, prefixes []netip.Prefix) bool {
|
|
for _, prefix := range prefixes {
|
|
if prefix.Contains(address) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|