// Copyright (c) 2021 Tailscale Inc & 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 !js
// +build !js

// Package tun creates a tuntap device, working around OS-specific
// quirks if necessary.
package tstun

import (
	"errors"
	"runtime"
	"strings"
	"time"

	"golang.zx2c4.com/wireguard/tun"
	"tailscale.com/envknob"
	"tailscale.com/types/logger"
)

// tunMTU is the MTU we set on tailscale's TUN interface. wireguard-go
// defaults to 1420 bytes, which only works if the "outer" MTU is 1500
// bytes. This breaks on DSL connections (typically 1492 MTU) and on
// GCE (1460 MTU?!).
//
// 1280 is the smallest MTU allowed for IPv6, which is a sensible
// "probably works everywhere" setting until we develop proper PMTU
// discovery.
var tunMTU = 1280

func init() {
	if mtu, ok := envknob.LookupInt("TS_DEBUG_MTU"); ok {
		tunMTU = mtu
	}
}

// createTAP is non-nil on Linux.
var createTAP func(tapName, bridgeName string) (tun.Device, error)

// New returns a tun.Device for the requested device name, along with
// the OS-dependent name that was allocated to the device.
func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
	var dev tun.Device
	var err error
	if strings.HasPrefix(tunName, "tap:") {
		if runtime.GOOS != "linux" {
			return nil, "", errors.New("tap only works on Linux")
		}
		f := strings.Split(tunName, ":")
		var tapName, bridgeName string
		switch len(f) {
		case 2:
			tapName = f[1]
		case 3:
			tapName, bridgeName = f[1], f[2]
		default:
			return nil, "", errors.New("bogus tap argument")
		}
		dev, err = createTAP(tapName, bridgeName)
	} else {
		dev, err = tun.CreateTUN(tunName, tunMTU)
	}
	if err != nil {
		return nil, "", err
	}
	if err := waitInterfaceUp(dev, 90*time.Second, logf); err != nil {
		dev.Close()
		return nil, "", err
	}
	if err := setLinkAttrs(dev); err != nil {
		logf("setting link attributes: %v", err)
	}
	name, err := interfaceName(dev)
	if err != nil {
		dev.Close()
		return nil, "", err
	}
	return dev, name, nil
}

// tunDiagnoseFailure, if non-nil, does OS-specific diagnostics of why
// TUN failed to work.
var tunDiagnoseFailure func(tunName string, logf logger.Logf)

// Diagnose tries to explain a tuntap device creation failure.
// It pokes around the system and logs some diagnostic info that might
// help debug why tun creation failed. Because device creation has
// already failed and the program's about to end, log a lot.
func Diagnose(logf logger.Logf, tunName string) {
	if tunDiagnoseFailure != nil {
		tunDiagnoseFailure(tunName, logf)
	} else {
		logf("no TUN failure diagnostics for OS %q", runtime.GOOS)
	}
}
