// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build darwin || dragonfly || freebsd || netbsd || openbsd

// Package route provides basic functions for the manipulation of
// packet routing facilities on BSD variants.
//
// The package supports any version of Darwin, any version of
// DragonFly BSD, FreeBSD 7 and above, NetBSD 6 and above, and OpenBSD
// 5.6 and above.
package route

import (
	"errors"
	"os"
	"syscall"
)

var (
	errUnsupportedMessage = errors.New("unsupported message")
	errMessageMismatch    = errors.New("message mismatch")
	errMessageTooShort    = errors.New("message too short")
	errInvalidMessage     = errors.New("invalid message")
	errInvalidAddr        = errors.New("invalid address")
	errShortBuffer        = errors.New("short buffer")
)

// A RouteMessage represents a message conveying an address prefix, a
// nexthop address and an output interface.
//
// Unlike other messages, this message can be used to query adjacency
// information for the given address prefix, to add a new route, and
// to delete or modify the existing route from the routing information
// base inside the kernel by writing and reading route messages on a
// routing socket.
//
// For the manipulation of routing information, the route message must
// contain appropriate fields that include:
//
//	Version       = <must be specified>
//	Type          = <must be specified>
//	Flags         = <must be specified>
//	Index         = <must be specified if necessary>
//	ID            = <must be specified>
//	Seq           = <must be specified>
//	Addrs         = <must be specified>
//
// The Type field specifies a type of manipulation, the Flags field
// specifies a class of target information and the Addrs field
// specifies target information like the following:
//
//	route.RouteMessage{
//		Version: RTM_VERSION,
//		Type: RTM_GET,
//		Flags: RTF_UP | RTF_HOST,
//		ID: uintptr(os.Getpid()),
//		Seq: 1,
//		Addrs: []route.Addrs{
//			RTAX_DST: &route.Inet4Addr{ ... },
//			RTAX_IFP: &route.LinkAddr{ ... },
//			RTAX_BRD: &route.Inet4Addr{ ... },
//		},
//	}
//
// The values for the above fields depend on the implementation of
// each operating system.
//
// The Err field on a response message contains an error value on the
// requested operation. If non-nil, the requested operation is failed.
type RouteMessage struct {
	Version int     // message version
	Type    int     // message type
	Flags   int     // route flags
	Index   int     // interface index when attached
	ID      uintptr // sender's identifier; usually process ID
	Seq     int     // sequence number
	Err     error   // error on requested operation
	Addrs   []Addr  // addresses

	extOff int    // offset of header extension
	raw    []byte // raw message
}

// Marshal returns the binary encoding of m.
func (m *RouteMessage) Marshal() ([]byte, error) {
	return m.marshal()
}

// A RIBType represents a type of routing information base.
type RIBType int

const (
	RIBTypeRoute     RIBType = syscall.NET_RT_DUMP
	RIBTypeInterface RIBType = syscall.NET_RT_IFLIST
)

// FetchRIB fetches a routing information base from the operating
// system.
//
// The provided af must be an address family.
//
// The provided arg must be a RIBType-specific argument.
// When RIBType is related to routes, arg might be a set of route
// flags. When RIBType is related to network interfaces, arg might be
// an interface index or a set of interface flags. In most cases, zero
// means a wildcard.
func FetchRIB(af int, typ RIBType, arg int) ([]byte, error) {
	try := 0
	for {
		try++
		mib := [6]int32{syscall.CTL_NET, syscall.AF_ROUTE, 0, int32(af), int32(typ), int32(arg)}
		n := uintptr(0)
		if err := sysctl(mib[:], nil, &n, nil, 0); err != nil {
			return nil, os.NewSyscallError("sysctl", err)
		}
		if n == 0 {
			return nil, nil
		}
		b := make([]byte, n)
		if err := sysctl(mib[:], &b[0], &n, nil, 0); err != nil {
			// If the sysctl failed because the data got larger
			// between the two sysctl calls, try a few times
			// before failing. (golang.org/issue/45736).
			const maxTries = 3
			if err == syscall.ENOMEM && try < maxTries {
				continue
			}
			return nil, os.NewSyscallError("sysctl", err)
		}
		return b[:n], nil
	}
}
