Files
openlan/pkg/access/worker.go
2024-01-16 22:43:56 +08:00

405 lines
8.6 KiB
Go
Executable File

package access
import (
"crypto/tls"
"fmt"
"net"
"os"
"runtime"
"strings"
"time"
"github.com/luscis/openlan/pkg/config"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/models"
"github.com/luscis/openlan/pkg/network"
"github.com/luscis/openlan/pkg/schema"
)
type jobTimer struct {
Time int64
Call func() error
}
type KeepAlive struct {
Interval int64
LastTime int64
}
func (k *KeepAlive) Should() bool {
return time.Now().Unix()-k.LastTime >= k.Interval
}
func (k *KeepAlive) Update() {
k.LastTime = time.Now().Unix()
}
var (
EvSocConed = "conned"
EvSocRecon = "reconn"
EvSocClosed = "closed"
EvSocSuccess = "success"
EvSocSignIn = "signIn"
EvSocLogin = "login"
EvTapIpAddr = "ipAddr"
EvTapReadErr = "readErr"
EvTapReset = "reset"
EvTapOpenErr = "openErr"
)
type WorkerEvent struct {
Type string
Reason string
Time int64
Data interface{}
}
func (e *WorkerEvent) String() string {
return e.Type + " " + e.Reason
}
func NewEvent(newType, reason string) *WorkerEvent {
return &WorkerEvent{
Type: newType,
Time: time.Now().Unix(),
Reason: reason,
}
}
type WorkerListener struct {
AddAddr func(ipStr string) error
DelAddr func(ipStr string) error
OnTap func(w *TapWorker) error
AddRoutes func(routes []*models.Route) error
DelRoutes func(routes []*models.Route) error
}
type PrefixRule struct {
Type int
Destination net.IPNet
NextHop net.IP
}
func GetSocketClient(p *config.Point) libol.SocketClient {
crypt := p.Crypt
block := libol.NewBlockCrypt(crypt.Algo, crypt.Secret)
switch p.Protocol {
case "kcp":
c := libol.NewKcpConfig()
c.Block = block
c.RdQus = p.Queue.SockRd
c.WrQus = p.Queue.SockWr
return libol.NewKcpClient(p.Connection, c)
case "tcp":
c := &libol.TcpConfig{
Block: block,
RdQus: p.Queue.SockRd,
WrQus: p.Queue.SockWr,
}
return libol.NewTcpClient(p.Connection, c)
case "udp":
c := &libol.UdpConfig{
Block: block,
Timeout: time.Duration(p.Timeout) * time.Second,
RdQus: p.Queue.SockRd,
WrQus: p.Queue.SockWr,
}
return libol.NewUdpClient(p.Connection, c)
case "ws":
c := &libol.WebConfig{
RdQus: p.Queue.SockRd,
WrQus: p.Queue.SockWr,
}
return libol.NewWebClient(p.Connection, c)
case "wss":
c := &libol.WebConfig{
Block: block,
RdQus: p.Queue.SockRd,
WrQus: p.Queue.SockWr,
}
if p.Cert != nil {
c.Cert = &libol.CertConfig{
Insecure: p.Cert.Insecure,
RootCa: p.Cert.CaFile,
}
}
return libol.NewWebClient(p.Connection, c)
default:
c := &libol.TcpConfig{
Block: block,
RdQus: p.Queue.SockRd,
WrQus: p.Queue.SockWr,
}
if p.Cert != nil {
c.Tls = &tls.Config{
InsecureSkipVerify: p.Cert.Insecure,
RootCAs: p.Cert.GetCertPool(),
}
}
return libol.NewTcpClient(p.Connection, c)
}
}
func GetTapCfg(c *config.Point) network.TapConfig {
cfg := network.TapConfig{
Provider: c.Interface.Provider,
Name: c.Interface.Name,
Network: c.Interface.Address,
KernBuf: c.Queue.VirSnd,
VirBuf: c.Queue.VirWrt,
}
if c.Interface.Provider == "tun" {
cfg.Type = network.TUN
} else {
cfg.Type = network.TAP
}
return cfg
}
type Worker struct {
// private
ifAddr string
listener WorkerListener
conWorker *SocketWorker
tapWorker *TapWorker
cfg *config.Point
uuid string
network *models.Network
routes []PrefixRule
out *libol.SubLogger
done chan bool
ticker *time.Ticker
}
func NewWorker(cfg *config.Point) *Worker {
return &Worker{
ifAddr: cfg.Interface.Address,
cfg: cfg,
routes: make([]PrefixRule, 0, 32),
out: libol.NewSubLogger(cfg.Id()),
done: make(chan bool),
ticker: time.NewTicker(2 * time.Second),
}
}
func (w *Worker) Initialize() {
if w.cfg == nil {
return
}
pid := os.Getpid()
if fp, err := libol.OpenWrite(w.cfg.PidFile); err == nil {
_, _ = fp.WriteString(fmt.Sprintf("%d", pid))
}
w.out.Info("Worker.Initialize")
client := GetSocketClient(w.cfg)
w.conWorker = NewSocketWorker(client, w.cfg)
tapCfg := GetTapCfg(w.cfg)
// register listener
w.tapWorker = NewTapWorker(tapCfg, w.cfg)
w.conWorker.SetUUID(w.UUID())
w.conWorker.listener = SocketWorkerListener{
OnClose: w.OnClose,
OnSuccess: w.OnSuccess,
OnIpAddr: w.OnIpAddr,
ReadAt: w.tapWorker.Write,
}
w.conWorker.Initialize()
w.tapWorker.listener = TapWorkerListener{
OnOpen: func(t *TapWorker) error {
if w.listener.OnTap != nil {
if err := w.listener.OnTap(t); err != nil {
return err
}
}
if w.network != nil {
n := w.network
// remove older firstly
w.FreeIpAddr()
_ = w.OnIpAddr(w.conWorker, n)
}
return nil
},
ReadAt: w.conWorker.Write,
FindNext: w.FindNext,
}
w.tapWorker.Initialize()
}
func (w *Worker) FlushStatus() {
file := w.cfg.StatusFile
device := w.tapWorker.device
client := w.conWorker.client
if file == "" || device == nil || client == nil {
return
}
sts := client.Statistics()
status := &schema.Point{
RxBytes: uint64(sts[libol.CsRecvOkay]),
TxBytes: uint64(sts[libol.CsSendOkay]),
ErrPkt: uint64(sts[libol.CsSendError]),
Uptime: client.UpTime(),
State: client.Status().String(),
Device: device.Name(),
Network: w.cfg.Network,
Protocol: w.cfg.Protocol,
User: strings.SplitN(w.cfg.Username, "@", 2)[0],
Remote: w.cfg.Connection,
AliveTime: client.AliveTime(),
UUID: w.uuid,
Alias: w.cfg.Alias,
System: runtime.GOOS,
}
if w.network != nil {
status.Address = models.NewNetworkSchema(w.network)
}
_ = libol.MarshalSave(status, file, true)
}
func (w *Worker) Start() {
w.out.Debug("Worker.Start linux.")
w.FlushStatus()
w.tapWorker.Start()
w.conWorker.Start()
libol.Go(func() {
for {
select {
case <-w.done:
return
case <-w.ticker.C:
w.FlushStatus()
}
}
})
}
func (w *Worker) Stop() {
if w.tapWorker == nil || w.conWorker == nil {
return
}
w.done <- true
w.FreeIpAddr()
w.conWorker.Stop()
w.tapWorker.Stop()
w.conWorker = nil
w.tapWorker = nil
}
func (w *Worker) UpTime() int64 {
client := w.conWorker.client
if client != nil {
return client.AliveTime()
}
return 0
}
func (w *Worker) FindNext(dest []byte) []byte {
for _, rt := range w.routes {
if !rt.Destination.Contains(dest) {
continue
}
if rt.Type == 0x00 {
break
}
if w.out.Has(libol.DEBUG) {
w.out.Debug("Worker.FindNext %v to %v", dest, rt.NextHop)
}
return rt.NextHop.To4()
}
return dest
}
func (w *Worker) OnIpAddr(s *SocketWorker, n *models.Network) error {
addr := fmt.Sprintf("%s/%s", n.IfAddr, n.Netmask)
if models.NetworkEqual(w.network, n) {
w.out.Debug("Worker.OnIpAddr: %s noChanged", addr)
return nil
}
w.out.Cmd("Worker.OnIpAddr: %s", addr)
w.out.Cmd("Worker.OnIpAddr: %s", n.Routes)
prefix := libol.Netmask2Len(n.Netmask)
ipStr := fmt.Sprintf("%s/%d", n.IfAddr, prefix)
w.tapWorker.OnIpAddr(ipStr)
if w.listener.AddAddr != nil {
_ = w.listener.AddAddr(ipStr)
}
// Filter routes.
var routes []*models.Route
for _, rt := range n.Routes {
if _, _, err := net.ParseCIDR(rt.Prefix); err != nil {
w.out.Warn("Worker.OnIpAddr: parse %s failed.", rt.Prefix)
continue
}
if rt.NextHop == n.IfAddr || rt.Origin == n.IfAddr {
continue
}
routes = append(routes, rt)
}
if w.listener.AddRoutes != nil {
_ = w.listener.AddRoutes(routes)
}
w.network = n
// update routes
ip := net.ParseIP(w.network.IfAddr)
m := net.IPMask(net.ParseIP(w.network.Netmask).To4())
w.routes = append(w.routes, PrefixRule{
Type: 0x00,
Destination: net.IPNet{IP: ip.Mask(m), Mask: m},
NextHop: libol.EthZero,
})
for _, rt := range routes {
_, dest, _ := net.ParseCIDR(rt.Prefix)
w.routes = append(w.routes, PrefixRule{
Type: 0x01,
Destination: *dest,
NextHop: net.ParseIP(rt.NextHop),
})
}
return nil
}
func (w *Worker) FreeIpAddr() {
if w.network == nil {
return
}
if w.listener.DelRoutes != nil {
_ = w.listener.DelRoutes(w.network.Routes)
}
if w.listener.DelAddr != nil {
prefix := libol.Netmask2Len(w.network.Netmask)
ipStr := fmt.Sprintf("%s/%d", w.network.IfAddr, prefix)
_ = w.listener.DelAddr(ipStr)
}
w.network = nil
w.routes = make([]PrefixRule, 0, 32)
}
func (w *Worker) OnClose(s *SocketWorker) error {
w.out.Info("Worker.OnClose")
w.FreeIpAddr()
return nil
}
func (w *Worker) OnSuccess(s *SocketWorker) error {
w.out.Info("Worker.OnSuccess")
if !w.cfg.RequestAddr {
w.out.Info("SocketWorker.AddAddr: notAllowed")
} else if w.listener.AddAddr != nil {
_ = w.listener.AddAddr(w.ifAddr)
}
return nil
}
func (w *Worker) UUID() string {
if w.uuid == "" {
w.uuid = libol.GenString(13)
}
return w.uuid
}
func (w *Worker) SetUUID(v string) {
w.uuid = v
}