#! /usr/libexec/atf_pytest_wrapper -P /usr/tests
#
# Copyright (c) 2026 Stormshield
#
# SPDX-License-Identifier: BSD-2-Clause
#

import pytest
import errno
import os
import socket
import struct
import sys
import logging

from atf_python.sys.net.vnet import VnetTestTemplate
from atf_python.sys.net.tools import ToolsHelper

def _common(tunable, addr, domain, type, proto):
    """
    Test what happens when we try to bind a socket to an
    address that is not present in the current FIB.
    """
    sysctl_output = ToolsHelper.get_output(f"sysctl {tunable}")
    tunable_val = int(sysctl_output.split(":")[1])
    if tunable_val != 0:
        pytest.skip(f"{tunable} must be set to 0")

    port = 12345 if type != socket.SOCK_RAW else 0
    s = socket.socket(domain, type, proto)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_SETFIB, 0)

    failed = False
    try:
        s.bind((addr, port))
    except OSError as e:
        assert e.errno == errno.EADDRNOTAVAIL
        failed = True
    assert failed

    s.setsockopt(socket.SOL_SOCKET, socket.SO_SETFIB, 1)

    failed = False
    try:
        s.bind((addr, port))
    except OSError:
        failed = True
    assert not failed
    s.close()

class TestFibBind(VnetTestTemplate):
    REQUIRED_MODULES = []
    TOPOLOGY = {
        "vnet1": { "ifaces": [ "if1", "if2" ] },
        "if1": {
            "prefixes4": [("192.168.1.1/24", "192.168.1.2/24")],
            "fib": (0, 0),
        },
        "if2": {
            "prefixes4": [("192.168.2.1/24", "192.168.2.2/24")],
            "fib": (1, 0),
        },
    }

    def setup_method(self, method):
        super().setup_method(method)

    def test_TCP(self):
        _common("net.inet.tcp.bind_all_fibs", "192.168.2.1", socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)

    def test_UDP(self):
        _common("net.inet.udp.bind_all_fibs", "192.168.2.1", socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

    def test_RAW(self):
        _common("net.inet.raw.bind_all_fibs", "192.168.2.1", socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)

class TestFibBind6(VnetTestTemplate):
    REQUIRED_MODULES = []
    TOPOLOGY = {
        "vnet1": { "ifaces": [ "if1", "if2" ] },
        "if1": {
            "prefixes6": [("2001:db8:0:1::1/64", "2001:db8:0:1::2/64")],
            "fib": (0, 0),
        },
        "if2": {
            "prefixes6": [("2001:db8:0:2::1/64", "2001:db8:0:2::2/64")],
            "fib": (1, 0),
        },
    }

    def setup_method(self, method):
        super().setup_method(method)

    def test_TCP(self):
        _common("net.inet.tcp.bind_all_fibs", "2001:db8:0:2::1", socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP)

    def test_UDP(self):
        _common("net.inet.udp.bind_all_fibs", "2001:db8:0:2::1", socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

    def test_RAW(self):
        _common("net.inet.raw.bind_all_fibs", "2001:db8:0:2::1", socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_IPV6)
