[GET] /server

This commit is contained in:
Mike Schwörer 2023-12-01 11:24:22 +01:00
parent 1f4a477077
commit b958ff7ca2
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
7 changed files with 274 additions and 72 deletions

126
config.go
View File

@ -11,95 +11,95 @@ import (
const APILevel = 1 const APILevel = 1
type Config struct { var SelfProcessID int
Namespace string
GinDebug bool `env:"GINDEBUG"`
ReturnRawErrors bool `env:"RETURNERRORS"`
Custom404 bool `env:"CUSTOM404"`
LogLevel zerolog.Level `env:"LOGLEVEL"`
ServerIP string `env:"IP"`
ServerPort string `env:"PORT"`
RequestTimeout time.Duration `env:"REQUEST_TIMEOUT"`
Cors bool `env:"CORS"`
}
type MailConfig struct { type Config struct {
Host string `env:"HOST"` Namespace string
Port int `env:"PORT"` GinDebug bool `env:"GINDEBUG"`
Username string `env:"USERNAME"` ReturnRawErrors bool `env:"RETURNERRORS"`
Password string `env:"PASSWORD"` Custom404 bool `env:"CUSTOM404"`
Sender string `env:"SENDER"` LogLevel zerolog.Level `env:"LOGLEVEL"`
ServerIP string `env:"IP"`
ServerPort int `env:"PORT"`
RequestTimeout time.Duration `env:"REQUEST_TIMEOUT"`
Cors bool `env:"CORS"`
VerifyConnTimeout time.Duration `env:"VERIFY_CONN_TIMEOUT"`
} }
var Conf Config var Conf Config
var configLocHost = func() Config { var configLocHost = func() Config {
return Config{ return Config{
Namespace: "local", Namespace: "local",
GinDebug: true, GinDebug: true,
ServerIP: "0.0.0.0", ServerIP: "0.0.0.0",
ServerPort: "80", ServerPort: 80,
Custom404: true, Custom404: true,
ReturnRawErrors: true, ReturnRawErrors: true,
RequestTimeout: 16 * time.Second, RequestTimeout: 16 * time.Second,
LogLevel: zerolog.DebugLevel, LogLevel: zerolog.DebugLevel,
Cors: true, Cors: true,
VerifyConnTimeout: time.Second,
} }
} }
var configLocDocker = func() Config { var configLocDocker = func() Config {
return Config{ return Config{
Namespace: "local-docker", Namespace: "local-docker",
GinDebug: true, GinDebug: true,
ServerIP: "0.0.0.0", ServerIP: "0.0.0.0",
ServerPort: "80", ServerPort: 80,
Custom404: true, Custom404: true,
ReturnRawErrors: true, ReturnRawErrors: true,
RequestTimeout: 16 * time.Second, RequestTimeout: 16 * time.Second,
LogLevel: zerolog.DebugLevel, LogLevel: zerolog.DebugLevel,
Cors: true, Cors: true,
VerifyConnTimeout: time.Second,
} }
} }
var configDev = func() Config { var configDev = func() Config {
return Config{ return Config{
Namespace: "develop", Namespace: "develop",
GinDebug: true, GinDebug: true,
ServerIP: "0.0.0.0", ServerIP: "0.0.0.0",
ServerPort: "80", ServerPort: 80,
Custom404: false, Custom404: false,
ReturnRawErrors: false, ReturnRawErrors: false,
RequestTimeout: 16 * time.Second, RequestTimeout: 16 * time.Second,
LogLevel: zerolog.DebugLevel, LogLevel: zerolog.DebugLevel,
Cors: false, Cors: false,
VerifyConnTimeout: time.Second,
} }
} }
var configStag = func() Config { var configStag = func() Config {
return Config{ return Config{
Namespace: "staging", Namespace: "staging",
GinDebug: true, GinDebug: true,
ServerIP: "0.0.0.0", ServerIP: "0.0.0.0",
ServerPort: "80", ServerPort: 80,
Custom404: false, Custom404: false,
ReturnRawErrors: false, ReturnRawErrors: false,
RequestTimeout: 16 * time.Second, RequestTimeout: 16 * time.Second,
LogLevel: zerolog.DebugLevel, LogLevel: zerolog.DebugLevel,
Cors: false, Cors: false,
VerifyConnTimeout: time.Second,
} }
} }
var configProd = func() Config { var configProd = func() Config {
return Config{ return Config{
Namespace: "production", Namespace: "production",
GinDebug: false, GinDebug: false,
ServerIP: "0.0.0.0", ServerIP: "0.0.0.0",
ServerPort: "80", ServerPort: 80,
Custom404: false, Custom404: false,
ReturnRawErrors: false, ReturnRawErrors: false,
RequestTimeout: 16 * time.Second, RequestTimeout: 16 * time.Second,
LogLevel: zerolog.InfoLevel, LogLevel: zerolog.InfoLevel,
Cors: false, Cors: false,
VerifyConnTimeout: time.Second,
} }
} }
@ -143,4 +143,6 @@ func init() {
} }
Conf = cfg Conf = cfg
SelfProcessID = os.Getpid()
} }

View File

@ -136,9 +136,6 @@ func (j *JobRunner[TData]) execute() (err error) {
changes, err := langext.RunPanicSafeR2(func() (int, error) { return j.jobFunc(runCtx, j.app, lstr, j.data) }) changes, err := langext.RunPanicSafeR2(func() (int, error) { return j.jobFunc(runCtx, j.app, lstr, j.data) })
finCtx, cancelFinCtx := context.WithTimeout(context.Background(), time.Minute)
defer cancelFinCtx()
//goland:noinspection GoTypeAssertionOnErrors //goland:noinspection GoTypeAssertionOnErrors
if panicerr, ok := err.(langext.PanicWrappedErr); ok { if panicerr, ok := err.(langext.PanicWrappedErr); ok {

View File

@ -2,15 +2,23 @@ package logic
import ( import (
"context" "context"
"errors"
"fmt"
"github.com/cakturk/go-netstat/netstat" "github.com/cakturk/go-netstat/netstat"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gogs.mikescher.com/BlackForestBytes/goext/ginext" "gogs.mikescher.com/BlackForestBytes/goext/ginext"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/syncext" "gogs.mikescher.com/BlackForestBytes/goext/syncext"
"io"
bunny "locbunny" bunny "locbunny"
"locbunny/models" "locbunny/models"
"net" "net"
"net/http"
"os" "os"
"os/signal" "os/signal"
"strconv"
"strings"
"sync"
"syscall" "syscall"
"time" "time"
) )
@ -45,7 +53,7 @@ func (app *Application) Stop() {
func (app *Application) Run() { func (app *Application) Run() {
addr := net.JoinHostPort(app.Config.ServerIP, app.Config.ServerPort) addr := net.JoinHostPort(app.Config.ServerIP, strconv.Itoa(app.Config.ServerPort))
errChan, httpserver := app.Gin.ListenAndServeHTTP(addr, func(port string) { errChan, httpserver := app.Gin.ListenAndServeHTTP(addr, func(port string) {
app.Port = port app.Port = port
@ -104,18 +112,159 @@ func (app *Application) Run() {
func (app *Application) ListServer(ctx *ginext.AppContext) ([]models.Server, error) { func (app *Application) ListServer(ctx *ginext.AppContext) ([]models.Server, error) {
socks, err := netstat.TCPSocks(netstat.NoopFilter) socks4, err := netstat.TCPSocks(netstat.NoopFilter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
res := make([]models.Server, 0) socks6, err := netstat.TCP6Socks(netstat.NoopFilter)
if err != nil {
return nil, err
}
for _, sock := range socks { sockCount := len(socks4) + len(socks6)
res = append(res, models.Server{Port: int(sock.LocalAddr.Port)}) wg := sync.WaitGroup{}
echan := make(chan error, sockCount*3)
rchan := make(chan models.Server, sockCount*3)
for _i := range socks4 {
i := _i
wg.Add(1)
go func() {
defer wg.Done()
con1, err := app.verifyHTTPConn(socks4[i], "HTTP", "v4")
if err == nil {
rchan <- con1
return
} else {
echan <- err
}
}()
wg.Add(1)
go func() {
defer wg.Done()
con2, err := app.verifyHTTPConn(socks4[i], "HTTPS", "v4")
if err == nil {
rchan <- con2
return
} else {
echan <- err
}
}()
}
for _i := range socks6 {
i := _i
wg.Add(1)
go func() {
defer wg.Done()
con1, err := app.verifyHTTPConn(socks6[i], "HTTP", "v6")
if err == nil {
rchan <- con1
return
} else {
echan <- err
}
}()
wg.Add(1)
go func() {
defer wg.Done()
con2, err := app.verifyHTTPConn(socks6[i], "HTTPS", "v6")
if err == nil {
rchan <- con2
return
} else {
echan <- err
}
}()
}
wg.Wait()
close(echan)
close(rchan)
duplicates := make(map[int]bool, sockCount*3)
res := make([]models.Server, 0, sockCount*3)
for v := range rchan {
if _, ok := duplicates[v.Port]; !ok {
res = append(res, v)
duplicates[v.Port] = true
}
} }
return res, nil return res, nil
} }
func (app *Application) verifyHTTPConn(sock netstat.SockTabEntry, proto string, ipversion string) (models.Server, error) {
ctx, cancel := context.WithTimeout(context.Background(), bunny.Conf.VerifyConnTimeout)
defer cancel()
if sock.State != netstat.Listen {
log.Debug().Msg(fmt.Sprintf("Failed to verify socket [%s|%s] invalid state: %s", ipversion, strings.ToUpper(proto), sock.State.String()))
return models.Server{}, errors.New("invalid sock-state")
}
if int(sock.LocalAddr.Port) == bunny.Conf.ServerPort && sock.Process != nil && sock.Process.Pid == bunny.SelfProcessID {
log.Debug().Msg(fmt.Sprintf("Skip socket [%s|%s] (this is our own server)", ipversion, strings.ToUpper(proto)))
return models.Server{}, errors.New("skip self")
}
c := http.Client{}
url := fmt.Sprintf("%s://localhost:%d", strings.ToLower(proto), sock.LocalAddr.Port)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
log.Debug().Msg(fmt.Sprintf("Failed to create [%s|%s] request to %d", ipversion, strings.ToUpper(proto), sock.LocalAddr.Port))
return models.Server{}, err
}
resp1, err := c.Do(req)
if err != nil {
log.Debug().Msg(fmt.Sprintf("Failed to send [%s|%s] request to %s", ipversion, strings.ToUpper(proto), url))
return models.Server{}, err
}
defer func() { _ = resp1.Body.Close() }()
resbody, err := io.ReadAll(resp1.Body)
if err != nil {
log.Debug().Msg(fmt.Sprintf("Failed to read [%s|%s] response from %s", ipversion, strings.ToUpper(proto), url))
return models.Server{}, err
}
ct := resp1.Header.Get("Content-Type")
if ct != "" {
var pnm *string = nil
var pid *int = nil
if sock.Process != nil {
pnm = langext.Ptr(sock.Process.Name)
pid = langext.Ptr(sock.Process.Pid)
}
return models.Server{
Port: int(sock.LocalAddr.Port),
IP: sock.LocalAddr.IP.String(),
Protocol: proto,
StatusCode: resp1.StatusCode,
Response: string(resbody),
ContentType: ct,
Process: pnm,
PID: pid,
UID: sock.UID,
SockState: sock.State.String(),
}, nil
}
log.Debug().Msg(fmt.Sprintf("Failed to categorize [%s|%s] response from %s (Content-Type: '%s')", ipversion, strings.ToUpper(proto), url, ct))
return models.Server{}, errors.New("invalid response-type")
}

View File

@ -7,7 +7,7 @@ import "go.mongodb.org/mongo-driver/bson/bsontype"
import "go.mongodb.org/mongo-driver/bson/primitive" import "go.mongodb.org/mongo-driver/bson/primitive"
import "gogs.mikescher.com/BlackForestBytes/goext/exerr" import "gogs.mikescher.com/BlackForestBytes/goext/exerr"
const ChecksumIDGenerator = "8fa696914bf8d1c1c4b9f80be45d0a9dfbd0fed789856bf84ced2979c789b958" // GoExtVersion: 0.0.288 const ChecksumIDGenerator = "cf5fe3d14932e535744418c0aa3751c08f392f9f117bfa5e8b04f70379879131" // GoExtVersion: 0.0.288
// ================================ AnyID (ids.go) ================================ // ================================ AnyID (ids.go) ================================

View File

@ -1,5 +1,14 @@
package models package models
type Server struct { type Server struct {
Port int Port int `json:"port"`
IP string `json:"ip"`
Protocol string `json:"protocol"`
StatusCode int `json:"statusCode"`
Response string `json:"response"`
ContentType string `json:"contentType"`
Process *string `json:"process"`
PID *int `json:"pid"`
UID uint32 `json:"uid"`
SockState string `json:"sockState"`
} }

View File

@ -283,8 +283,35 @@
"models.Server": { "models.Server": {
"type": "object", "type": "object",
"properties": { "properties": {
"contentType": {
"type": "string"
},
"ip": {
"type": "string"
},
"pid": {
"type": "integer"
},
"port": { "port": {
"type": "integer" "type": "integer"
},
"process": {
"type": "string"
},
"protocol": {
"type": "string"
},
"response": {
"type": "string"
},
"sockState": {
"type": "string"
},
"statusCode": {
"type": "integer"
},
"uid": {
"type": "integer"
} }
} }
} }

View File

@ -56,8 +56,26 @@ definitions:
type: object type: object
models.Server: models.Server:
properties: properties:
contentType:
type: string
ip:
type: string
pid:
type: integer
port: port:
type: integer type: integer
process:
type: string
protocol:
type: string
response:
type: string
sockState:
type: string
statusCode:
type: integer
uid:
type: integer
type: object type: object
host: localhost host: localhost
info: info: