mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 15:05:28 +02:00
refactor(server): Improve Wireguard analyzer
Signed-off-by: Myzel394 <github.7a2op@simplelogin.co>
This commit is contained in:
parent
36950fe271
commit
eb076dbf53
45
server/handlers/wireguard/analyzer/analyzer.go
Normal file
45
server/handlers/wireguard/analyzer/analyzer.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"config-lsp/handlers/wireguard"
|
||||||
|
"config-lsp/handlers/wireguard/indexes"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
type analyzerContext struct {
|
||||||
|
document *wireguard.WGDocument
|
||||||
|
diagnostics []protocol.Diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
func Analyze(
|
||||||
|
d *wireguard.WGDocument,
|
||||||
|
) []protocol.Diagnostic {
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzeStructureIsValid(ctx)
|
||||||
|
|
||||||
|
if len(ctx.diagnostics) > 0 {
|
||||||
|
return ctx.diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
|
i, indexErrors := indexes.CreateIndexes(d.Config)
|
||||||
|
|
||||||
|
if len(indexErrors) > 0 {
|
||||||
|
return common.ErrsToDiagnostics(indexErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Indexes = i
|
||||||
|
|
||||||
|
analyzeInterfaceSection(ctx)
|
||||||
|
analyzeDNSPropertyContainsFallback(ctx)
|
||||||
|
analyzeKeepAlivePropertyIsSet(ctx)
|
||||||
|
analyzeSymmetricPropertiesSet(ctx)
|
||||||
|
analyzeDuplicateAllowedIPs(ctx)
|
||||||
|
|
||||||
|
return ctx.diagnostics
|
||||||
|
}
|
57
server/handlers/wireguard/analyzer/analyzer_test.go
Normal file
57
server/handlers/wireguard/analyzer/analyzer_test.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/handlers/wireguard/parser"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMultipleIntefaces(t *testing.T) {
|
||||||
|
content := utils.Dedent(`
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = abc
|
||||||
|
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = def
|
||||||
|
`)
|
||||||
|
p := parser.CreateWireguardParser()
|
||||||
|
p.ParseFromString(content)
|
||||||
|
|
||||||
|
diagnostics := Analyze(p)
|
||||||
|
|
||||||
|
if len(diagnostics) == 0 {
|
||||||
|
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidValue(t *testing.T) {
|
||||||
|
content := utils.Dedent(`
|
||||||
|
[Interface]
|
||||||
|
DNS = nope
|
||||||
|
`)
|
||||||
|
p := parser.CreateWireguardParser()
|
||||||
|
p.ParseFromString(content)
|
||||||
|
|
||||||
|
diagnostics := Analyze(p)
|
||||||
|
|
||||||
|
if len(diagnostics) == 0 {
|
||||||
|
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateProperties(t *testing.T) {
|
||||||
|
content := utils.Dedent(`
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = abc
|
||||||
|
DNS = 1.1.1.1
|
||||||
|
PrivateKey = def
|
||||||
|
`)
|
||||||
|
p := parser.CreateWireguardParser()
|
||||||
|
p.ParseFromString(content)
|
||||||
|
|
||||||
|
diagnostics := Analyze(p)
|
||||||
|
|
||||||
|
if len(diagnostics) == 0 {
|
||||||
|
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
||||||
|
}
|
||||||
|
}
|
136
server/handlers/wireguard/analyzer/property.go
Normal file
136
server/handlers/wireguard/analyzer/property.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func analyzeDNSPropertyContainsFallback(
|
||||||
|
ctx *analyzerContext,
|
||||||
|
) {
|
||||||
|
sections, found := ctx.document.Indexes.SectionsByName["Interface"]
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaceSection := sections[0]
|
||||||
|
|
||||||
|
property := interfaceSection.FindFirstPropertyByName("DNS")
|
||||||
|
|
||||||
|
if property == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsAmount := len(strings.Split(property.Value.Value, ","))
|
||||||
|
|
||||||
|
if dnsAmount == 1 {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "There is only one DNS server specified. It is recommended to set up fallback DNS servers",
|
||||||
|
Severity: &common.SeverityHint,
|
||||||
|
Range: property.Value.ToLSPRange(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func analyzeKeepAlivePropertyIsSet(
|
||||||
|
ctx *analyzerContext,
|
||||||
|
) {
|
||||||
|
for _, section := range ctx.document.Indexes.SectionsByName["Peer"] {
|
||||||
|
// If an endpoint is set, then we should only check for the keepalive property
|
||||||
|
if section.FindFirstPropertyByName("Endpoint") != nil && section.FindFirstPropertyByName("PersistentKeepalive") == nil {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "PersistentKeepalive is not set. It is recommended to set this property, as it helps to maintain the connection when users are behind NAT",
|
||||||
|
Severity: &common.SeverityHint,
|
||||||
|
Range: section.Header.ToLSPRange(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func analyzeSymmetricPropertiesSet(
|
||||||
|
ctx *analyzerContext,
|
||||||
|
) {
|
||||||
|
for _, section := range ctx.document.Indexes.SectionsByName["Interface"] {
|
||||||
|
preUpProperty := section.FindFirstPropertyByName("PreUp")
|
||||||
|
preDownProperty := section.FindFirstPropertyByName("PreDown")
|
||||||
|
|
||||||
|
postUpProperty := section.FindFirstPropertyByName("PostUp")
|
||||||
|
postDownProperty := section.FindFirstPropertyByName("PostDown")
|
||||||
|
|
||||||
|
if preUpProperty != nil && preDownProperty == nil {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically",
|
||||||
|
Range: preUpProperty.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityHint,
|
||||||
|
})
|
||||||
|
} else if preUpProperty == nil && preDownProperty != nil {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "PreDown is set, but PreUp is not. It is recommended to set both properties symmetrically",
|
||||||
|
Range: preDownProperty.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityHint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if postUpProperty != nil && postDownProperty == nil {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically",
|
||||||
|
Range: postUpProperty.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityHint,
|
||||||
|
})
|
||||||
|
} else if postUpProperty == nil && postDownProperty != nil {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "PostDown is set, but PostUp is not. It is recommended to set both properties symmetrically",
|
||||||
|
Range: postDownProperty.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityHint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy
|
||||||
|
// Simply compare the host bits of the IP addresses.
|
||||||
|
// Use a binary tree to store the host bits.
|
||||||
|
func analyzeDuplicateAllowedIPs(
|
||||||
|
ctx *analyzerContext,
|
||||||
|
) {
|
||||||
|
ipHostSet := utils.CreateIPv4HostSet()
|
||||||
|
|
||||||
|
for _, section := range ctx.document.Indexes.SectionsByName["Peer"] {
|
||||||
|
property := section.FindFirstPropertyByName("AllowedIPs")
|
||||||
|
|
||||||
|
if property == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddress, err := netip.ParsePrefix(property.Value.Value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// This should not happen...
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipContext, _ := ipHostSet.ContainsIP(ipAddress); ipContext != nil {
|
||||||
|
definedLine := (*ipContext).Value("line").(uint32)
|
||||||
|
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: fmt.Sprintf("This IP range is already covered on line %d", definedLine+1),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
Range: property.ToLSPRange(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ipContext := context.WithValue(context.Background(), "line", property.Start.Line)
|
||||||
|
|
||||||
|
ipHostSet.AddIP(
|
||||||
|
ipAddress,
|
||||||
|
ipContext,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
server/handlers/wireguard/analyzer/section.go
Normal file
18
server/handlers/wireguard/analyzer/section.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func analyzeInterfaceSection(ctx *analyzerContext) {
|
||||||
|
sections := ctx.document.Indexes.SectionsByName["Interface"]
|
||||||
|
if len(sections) > 1 {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "Only one [Interface] section is allowed",
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
Range: sections[1].Header.ToLSPRange(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
83
server/handlers/wireguard/analyzer/structure.go
Normal file
83
server/handlers/wireguard/analyzer/structure.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
|
"config-lsp/handlers/wireguard/fields"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func analyzeStructureIsValid(ctx *analyzerContext) {
|
||||||
|
for _, section := range ctx.document.Config.Sections {
|
||||||
|
// Whether to check if the property is allowed in the section
|
||||||
|
checkAllowedProperty := true
|
||||||
|
|
||||||
|
if section.Header.Name == "" {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "This section is missing a name",
|
||||||
|
Range: section.Header.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
} else if !utils.KeyExists(fields.OptionsHeaderMap, section.Header.Name) {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: fmt.Sprintf("Unknown section '%s'. It must be one of: [Interface], [Peer]", section.Header.Name),
|
||||||
|
Range: section.Header.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
// Do not check as the section is unknown
|
||||||
|
checkAllowedProperty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(section.Properties) == 0 {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "This section is empty",
|
||||||
|
Range: section.Header.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityInformation,
|
||||||
|
Tags: []protocol.DiagnosticTag{
|
||||||
|
protocol.DiagnosticTagUnnecessary,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
existingProperties := make(map[string]*ast.WGProperty)
|
||||||
|
|
||||||
|
for _, property := range section.Properties {
|
||||||
|
if property.Key.Name == "" {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "This property is missing a name",
|
||||||
|
Range: property.Key.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value == nil || property.Value.Value == "" {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "This property is missing a value",
|
||||||
|
Range: property.Value.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkAllowedProperty {
|
||||||
|
options := fields.OptionsHeaderMap[section.Header.Name]
|
||||||
|
|
||||||
|
if !utils.KeyExists(options, property.Key.Name) {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: fmt.Sprintf("Unknown property '%s'", property.Key.Name),
|
||||||
|
Range: property.Key.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
} else if existingProperty, found := existingProperties[property.Key.Name]; found {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: fmt.Sprintf("Property '%s' has already been defined on line %d", property.Key.Name, existingProperty.Start.Line+1),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
Range: existingProperty.ToLSPRange(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user