Skip to content

Commit

Permalink
minimal working run version
Browse files Browse the repository at this point in the history
  • Loading branch information
reddec committed Mar 18, 2021
1 parent 69c1107 commit 0a36a2c
Show file tree
Hide file tree
Showing 7 changed files with 724 additions and 46 deletions.
139 changes: 118 additions & 21 deletions cmd/tinc-boot/run/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,33 @@ package run
import (
"context"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/reddec/tinc-boot/tincd/config"
"github.com/reddec/tinc-boot/tincd/daemon"
"github.com/reddec/tinc-boot/tincd/daemon/utils"
"github.com/reddec/tinc-boot/tincd/discovery"
"github.com/reddec/tinc-boot/types"
)

type Cmd struct {
Name string `long:"name" env:"NAME" description:"Node name. If not set - saved or hostname with random suffix will be used"`
Advertise []string `short:"a" long:"advertise" env:"ADVERTISE" description:"Routable IPs or IPs with ports that will be advertised for new clients. If not set - saved or all non-loopback IPs will be used"`
Port uint16 `short:"p" long:"port" env:"PORT" description:"Tinc listen port for fresh node. If not set - saved or random will be generated in 30000-40000 range"`
IP string `long:"ip" env:"IP" description:"VPN IP for fresh node. If not set - saved or random will be generated once in 172.0.0.0/8"`
Dir string `short:"d" long:"dir" env:"DIR" description:"tinc-boot directory. Will be created if not exists" default:"vpn"`
Tincd string `long:"tincd" env:"TINCD" description:"tincd binary location" default:"tincd"`
Name string `short:"n" long:"name" env:"NAME" description:"Node name. If not set - saved or hostname with random suffix will be used"`
Advertise []string `short:"a" long:"advertise" env:"ADVERTISE" description:"Routable IPs/domains with or without port that will be advertised for new clients. If not set - saved or all non-loopback IPs will be used"`
Port uint16 `short:"p" long:"port" env:"PORT" description:"Tinc listen port for fresh node. If not set - saved or random will be generated in 30000-40000 range"`
Device string `long:"device" env:"DEVICE" description:"Device name. If not defined - will use last 5 symbols of resolved name"`
IP string `long:"ip" env:"IP" description:"VPN IP for fresh node. If not set - saved or random will be generated once in 172.0.0.0/8"`
Dir string `short:"d" long:"dir" env:"DIR" description:"tinc-boot directory. Will be created if not exists" default:"vpn"`
Tincd string `long:"tincd" env:"TINCD" description:"tincd binary location" default:"tincd"`
DiscoveryInterval time.Duration `long:"discovery-interval" env:"DISCOVERY_INTERVAL" description:"Interval between discovery" default:"5s"`
}

func (cmd Cmd) configDir() string {
Expand All @@ -43,9 +48,26 @@ func (cmd Cmd) workDir() string {
return filepath.Join(cmd.Dir, "run")
}

func (cmd Cmd) ssdFile() string {
return filepath.Join(cmd.workDir(), "discovery.json")
}

func (cmd Cmd) clockFile() string {
return filepath.Join(cmd.workDir(), "clock")
}

func (cmd Cmd) advertise() []string {
if len(cmd.Advertise) > 0 {
return cmd.Advertise
var ans = make([]string, 0, len(cmd.Advertise))
for _, addr := range cmd.Advertise {
host, port, err := net.SplitHostPort(addr)
if err != nil {
log.Println("parse address", addr, ":", err)
continue
}
ans = append(ans, host+" "+port)
}
return ans
}
ips, err := getAllRoutableIPs()
if err != nil {
Expand All @@ -62,12 +84,25 @@ func (cmd Cmd) port() uint16 {
return uint16(30000 + rand.Intn(10000))
}

func (cmd Cmd) name() string {
func (cmd *Cmd) name() string {
if cmd.Name != "" {
return cmd.Name
}
name, _ := os.Hostname()
return types.CleanString(name + utils.RandStringRunes(5))

cmd.Name = types.CleanString(name + utils.RandStringRunes(5))
return cmd.Name
}

func (cmd Cmd) deviceName() string {
if cmd.Device != "" {
return cmd.Device
}
name := cmd.name()
if len(name) <= 5 {
return name
}
return name[len(name)-5:]
}

func (cmd Cmd) ip() string {
Expand All @@ -90,14 +125,69 @@ func (cmd *Cmd) Execute([]string) error {
return fmt.Errorf("create work dir: %w", err)
}

tick, err := cmd.nextTick()
if err != nil {
return fmt.Errorf("count clock tick: %w", err)
}

ssd := discovery.NewSSD(cmd.ssdFile())

if err := ssd.Read(); err != nil {
return fmt.Errorf("read discovery: %w", err)
}

ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
defer cancel()

daemonConfig := daemon.Default(cmd.configDir())
daemonConfig.PidFile = filepath.Join(cmd.workDir(), "pid.run")

if !daemonConfig.Configured() {
log.Println("configuration not exists or invalid - creating a new one")
err := cmd.createConfig(ctx, daemonConfig)
if err != nil {
return fmt.Errorf("create config: %w", err)
}
} else {
log.Println("using existent configuration")
}

main, _, err := config.ReadNodeConfig(daemonConfig.ConfigDir)
if err != nil {
return fmt.Errorf("read generated config: %w", err)
}

ssd.Replace(discovery.Entity{
Name: main.Name,
Version: tick,
})

err = ssd.Save() // replace self discovery
if err != nil {
log.Println("save discovery meta config (fallback to in-memory only):", err)
}

discoveryService := discovery.New(ssd, daemonConfig, cmd.DiscoveryInterval)

daemonConfig.Events().SubscribeAll(discoveryService)

instance, err := daemonConfig.Spawn(ctx)
if err != nil {
return fmt.Errorf("spawn daemon: %w", err)
}
defer instance.Stop()

<-instance.Done()

return nil
}

func (cmd Cmd) createConfig(ctx context.Context, daemonConfig *daemon.Config) error {
var main = config.Main{
Name: cmd.name(),
Port: cmd.port(),
LocalDiscovery: true,
Interface: "tun" + strings.ToUpper(utils.RandStringRunes(5)),
Interface: "tun" + strings.ToUpper(cmd.deviceName()),
}
if err := config.SaveFile(cmd.tincFile(), main); err != nil {
return fmt.Errorf("create tinc.conf file: %w", err)
Expand All @@ -114,22 +204,29 @@ func (cmd *Cmd) Execute([]string) error {
return fmt.Errorf("create node file: %w", err)
}

ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
defer cancel()

if err := daemonConfig.Keygen(ctx, 4096); err != nil {
return fmt.Errorf("generate keys: %w", err)
}

instance, err := daemonConfig.Spawn(ctx)
if err != nil {
return fmt.Errorf("spawn daemon: %w", err)
return nil
}

func (cmd Cmd) nextTick() (int64, error) {
data, err := ioutil.ReadFile(cmd.clockFile())
if os.IsNotExist(err) {
data = []byte("0")
} else if err != nil {
return 0, fmt.Errorf("read clock: %w", err)
}
defer instance.Stop()

<-instance.Done()
tick, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
if err != nil {
// broken clock
tick = 0
}

return nil
tick++
return tick, ioutil.WriteFile(cmd.clockFile(), []byte(strconv.FormatInt(tick, 10)), 0755)
}

func getAllRoutableIPs() ([]string, error) {
Expand All @@ -139,8 +236,8 @@ func getAllRoutableIPs() ([]string, error) {
}
var ans []string
for _, addr := range addrs {
if ipaddr, ok := addr.(*net.IPAddr); ok {
if !ipaddr.IP.IsLoopback() {
if ipaddr, ok := addr.(*net.IPNet); ok {
if !ipaddr.IP.IsLoopback() && ipaddr.IP.IsGlobalUnicast() {
ans = append(ans, ipaddr.IP.String())
}
}
Expand Down
61 changes: 41 additions & 20 deletions tincd/daemon/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,90 @@ package daemon

import "sync"

type SubnetAdded struct {
type Configured struct {
lock sync.RWMutex
handlers []func(EventSubnetAdded)
handlers []func(Configuration)
}

func (ev *SubnetAdded) Subscribe(handler func(EventSubnetAdded)) {
func (ev *Configured) Subscribe(handler func(Configuration)) {
ev.lock.Lock()
ev.handlers = append(ev.handlers, handler)
ev.lock.Unlock()
}
func (ev *SubnetAdded) emit(payload EventSubnetAdded) {
func (ev *Configured) emit(payload Configuration) {
ev.lock.RLock()
for _, handler := range ev.handlers {
handler(payload)
}
ev.lock.RUnlock()
}

type SubnetRemoved struct {
type Stopped struct {
lock sync.RWMutex
handlers []func(EventSubnetRemoved)
handlers []func(Configuration)
}

func (ev *SubnetRemoved) Subscribe(handler func(EventSubnetRemoved)) {
func (ev *Stopped) Subscribe(handler func(Configuration)) {
ev.lock.Lock()
ev.handlers = append(ev.handlers, handler)
ev.lock.Unlock()
}
func (ev *SubnetRemoved) emit(payload EventSubnetRemoved) {
func (ev *Stopped) emit(payload Configuration) {
ev.lock.RLock()
for _, handler := range ev.handlers {
handler(payload)
}
ev.lock.RUnlock()
}

type Ready struct {
type SubnetAdded struct {
lock sync.RWMutex
handlers []func(EventReady)
handlers []func(EventSubnetAdded)
}

func (ev *Ready) Subscribe(handler func(EventReady)) {
func (ev *SubnetAdded) Subscribe(handler func(EventSubnetAdded)) {
ev.lock.Lock()
ev.handlers = append(ev.handlers, handler)
ev.lock.Unlock()
}
func (ev *Ready) emit() {
payload := EventReady{}
func (ev *SubnetAdded) emit(payload EventSubnetAdded) {
ev.lock.RLock()
for _, handler := range ev.handlers {
handler(payload)
}
ev.lock.RUnlock()
}

type Configured struct {
type SubnetRemoved struct {
lock sync.RWMutex
handlers []func(EventSubnetRemoved)
}

func (ev *SubnetRemoved) Subscribe(handler func(EventSubnetRemoved)) {
ev.lock.Lock()
ev.handlers = append(ev.handlers, handler)
ev.lock.Unlock()
}
func (ev *SubnetRemoved) emit(payload EventSubnetRemoved) {
ev.lock.RLock()
for _, handler := range ev.handlers {
handler(payload)
}
ev.lock.RUnlock()
}

type Ready struct {
lock sync.RWMutex
handlers []func(EventConfigured)
handlers []func(EventReady)
}

func (ev *Configured) Subscribe(handler func(EventConfigured)) {
func (ev *Ready) Subscribe(handler func(EventReady)) {
ev.lock.Lock()
ev.handlers = append(ev.handlers, handler)
ev.lock.Unlock()
}
func (ev *Configured) emit(payload EventConfigured) {
func (ev *Ready) emit() {
payload := EventReady{}
ev.lock.RLock()
for _, handler := range ev.handlers {
handler(payload)
Expand All @@ -76,20 +94,23 @@ func (ev *Configured) emit(payload EventConfigured) {
}

type Events struct {
Configured Configured
Stopped Stopped
SubnetAdded SubnetAdded
SubnetRemoved SubnetRemoved
Ready Ready
Configured Configured
}

func (bus *Events) SubscribeAll(listener interface {
Configured(payload Configuration)
Stopped(payload Configuration)
SubnetAdded(payload EventSubnetAdded)
SubnetRemoved(payload EventSubnetRemoved)
Ready(payload EventReady)
Configured(payload EventConfigured)
}) {
bus.Configured.Subscribe(listener.Configured)
bus.Stopped.Subscribe(listener.Stopped)
bus.SubnetAdded.Subscribe(listener.SubnetAdded)
bus.SubnetRemoved.Subscribe(listener.SubnetRemoved)
bus.Ready.Subscribe(listener.Ready)
bus.Configured.Subscribe(listener.Configured)
}
Loading

0 comments on commit 0a36a2c

Please sign in to comment.