Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0bf77b88c | ||
|
|
306e2e52d2 | ||
|
|
80bb7d45f8 | ||
|
|
25aa16be14 | ||
|
|
6b07c9e2e1 | ||
|
|
dfa7e1697d |
@@ -78,12 +78,14 @@ if (-not (Test-Path $OutputDir)) {
|
|||||||
Write-Host "Created output directory: $OutputDir" -ForegroundColor Green
|
Write-Host "Created output directory: $OutputDir" -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build flags
|
# Build flags — always inject version and copyright year; release builds also strip debug symbols.
|
||||||
$ldflags = ""
|
$buildYear = (Get-Date).Year
|
||||||
|
$injectedFlags = "-X main.version=$gitTag -X main.copyrightYear=$buildYear"
|
||||||
if ($Release) {
|
if ($Release) {
|
||||||
$ldflags = "-s -w"
|
$ldflags = "-s -w $injectedFlags"
|
||||||
Write-Host "Building RELEASE version (optimized, no debug symbols)..." -ForegroundColor Green
|
Write-Host "Building RELEASE version (optimized, no debug symbols)..." -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
|
$ldflags = $injectedFlags
|
||||||
Write-Host "Building DEBUG version (with debug symbols)..." -ForegroundColor Yellow
|
Write-Host "Building DEBUG version (with debug symbols)..." -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ import (
|
|||||||
const (
|
const (
|
||||||
StartMarker = "# ekDNSHelper <<-> START CONFIG"
|
StartMarker = "# ekDNSHelper <<-> START CONFIG"
|
||||||
EndMarker = "# ekDNSHelper <->> END CONFIG"
|
EndMarker = "# ekDNSHelper <->> END CONFIG"
|
||||||
|
|
||||||
|
// Legacy markers from the pre-rename "DNSHelper" era.
|
||||||
|
// ParseManagedBlock recognises these so that hosts files written by
|
||||||
|
// the old binary are detected, parsed, and silently upgraded to the
|
||||||
|
// current markers on the next write.
|
||||||
|
LegacyStartMarker = "# DNSHelper <<-> START CONFIG"
|
||||||
|
LegacyEndMarker = "# DNSHelper <->> END CONFIG"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSEntry represents a single IP-to-hostname mapping.
|
// DNSEntry represents a single IP-to-hostname mapping.
|
||||||
|
|||||||
+21
-4
@@ -17,13 +17,17 @@ func ParseManagedBlock(lines []string) (prefix, managed, postfix []string, hasMa
|
|||||||
endCount := 0
|
endCount := 0
|
||||||
|
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
if strings.Contains(line, "ekDNSHelper <<-> START CONFIG") {
|
// Recognise both current and legacy markers so that hosts files
|
||||||
|
// written by the old "DNSHelper" binary are parsed correctly.
|
||||||
|
if strings.Contains(line, "ekDNSHelper <<-> START CONFIG") ||
|
||||||
|
strings.Contains(line, "DNSHelper <<-> START CONFIG") {
|
||||||
startCount++
|
startCount++
|
||||||
if startIdx == -1 {
|
if startIdx == -1 {
|
||||||
startIdx = i
|
startIdx = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Contains(line, "ekDNSHelper <->> END CONFIG") {
|
if strings.Contains(line, "ekDNSHelper <->> END CONFIG") ||
|
||||||
|
strings.Contains(line, "DNSHelper <->> END CONFIG") {
|
||||||
endCount++
|
endCount++
|
||||||
if endIdx == -1 {
|
if endIdx == -1 {
|
||||||
endIdx = i
|
endIdx = i
|
||||||
@@ -57,6 +61,19 @@ func ParseManagedBlock(lines []string) (prefix, managed, postfix []string, hasMa
|
|||||||
return prefix, managed, postfix, true, nil
|
return prefix, managed, postfix, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lineMatchesHostname parses a hosts-file line ("IP<whitespace>hostname [aliases...]")
|
||||||
|
// and returns true if any hostname field (including aliases) matches,
|
||||||
|
// using case-insensitive comparison per RFC 4343.
|
||||||
|
func lineMatchesHostname(line, hostname string) bool {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
for _, f := range fields[1:] {
|
||||||
|
if strings.EqualFold(f, hostname) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// AddEntries removes existing entries for the same hostnames as the new entries,
|
// AddEntries removes existing entries for the same hostnames as the new entries,
|
||||||
// then appends the new entries. Returns deduplicated managed content lines.
|
// then appends the new entries. Returns deduplicated managed content lines.
|
||||||
func AddEntries(existing []string, entries []DNSEntry) []string {
|
func AddEntries(existing []string, entries []DNSEntry) []string {
|
||||||
@@ -71,7 +88,7 @@ func AddEntries(existing []string, entries []DNSEntry) []string {
|
|||||||
for _, line := range existing {
|
for _, line := range existing {
|
||||||
keep := true
|
keep := true
|
||||||
for h := range hostnames {
|
for h := range hostnames {
|
||||||
if strings.Contains(line, h) {
|
if lineMatchesHostname(line, h) {
|
||||||
keep = false
|
keep = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -101,7 +118,7 @@ func RemoveByHostname(existing []string, hostnames []string) []string {
|
|||||||
for _, line := range existing {
|
for _, line := range existing {
|
||||||
shouldRemove := false
|
shouldRemove := false
|
||||||
for _, host := range hostnames {
|
for _, host := range hostnames {
|
||||||
if strings.Contains(line, host) {
|
if lineMatchesHostname(line, host) {
|
||||||
shouldRemove = true
|
shouldRemove = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,134 @@ func TestParseManagedBlock_CorruptDuplicateMarkers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ParseManagedBlock — legacy marker tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestParseManagedBlock_LegacyMarkers(t *testing.T) {
|
||||||
|
lines := []string{
|
||||||
|
"127.0.0.1 localhost",
|
||||||
|
hostfile.LegacyStartMarker,
|
||||||
|
"1.1.1.1\thost1",
|
||||||
|
"2.2.2.2\thost2",
|
||||||
|
hostfile.LegacyEndMarker,
|
||||||
|
"# trailing comment",
|
||||||
|
}
|
||||||
|
prefix, managed, postfix, has, err := hostfile.ParseManagedBlock(lines)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatal("expected HasManagedBlock=true for legacy markers")
|
||||||
|
}
|
||||||
|
if len(prefix) != 1 || prefix[0] != "127.0.0.1 localhost" {
|
||||||
|
t.Errorf("unexpected prefix: %v", prefix)
|
||||||
|
}
|
||||||
|
if len(managed) != 2 {
|
||||||
|
t.Errorf("expected 2 managed lines, got %v", managed)
|
||||||
|
}
|
||||||
|
if len(postfix) != 1 || postfix[0] != "# trailing comment" {
|
||||||
|
t.Errorf("unexpected postfix: %v", postfix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseManagedBlock_LegacyStartMarkerAtLine0(t *testing.T) {
|
||||||
|
lines := []string{
|
||||||
|
hostfile.LegacyStartMarker,
|
||||||
|
"1.1.1.1\thost1",
|
||||||
|
hostfile.LegacyEndMarker,
|
||||||
|
}
|
||||||
|
prefix, managed, postfix, has, err := hostfile.ParseManagedBlock(lines)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatal("expected HasManagedBlock=true")
|
||||||
|
}
|
||||||
|
if len(prefix) != 0 {
|
||||||
|
t.Errorf("expected empty prefix, got %v", prefix)
|
||||||
|
}
|
||||||
|
if len(managed) != 1 || managed[0] != "1.1.1.1\thost1" {
|
||||||
|
t.Errorf("unexpected managed: %v", managed)
|
||||||
|
}
|
||||||
|
if len(postfix) != 0 {
|
||||||
|
t.Errorf("expected empty postfix, got %v", postfix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseManagedBlock_LegacyCorruptStartWithoutEnd(t *testing.T) {
|
||||||
|
lines := []string{
|
||||||
|
"127.0.0.1 localhost",
|
||||||
|
hostfile.LegacyStartMarker,
|
||||||
|
"1.1.1.1\thost1",
|
||||||
|
}
|
||||||
|
_, _, _, _, err := hostfile.ParseManagedBlock(lines)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for legacy start marker without end marker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseManagedBlock_LegacyCorruptEndWithoutStart(t *testing.T) {
|
||||||
|
lines := []string{
|
||||||
|
"127.0.0.1 localhost",
|
||||||
|
"1.1.1.1\thost1",
|
||||||
|
hostfile.LegacyEndMarker,
|
||||||
|
}
|
||||||
|
_, _, _, _, err := hostfile.ParseManagedBlock(lines)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for legacy end marker without start marker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseManagedBlock_MixedOldStartNewEnd(t *testing.T) {
|
||||||
|
// Legacy start with current end — still a valid block (both substrings match).
|
||||||
|
lines := []string{
|
||||||
|
"127.0.0.1 localhost",
|
||||||
|
hostfile.LegacyStartMarker,
|
||||||
|
"1.1.1.1\thost1",
|
||||||
|
hostfile.EndMarker,
|
||||||
|
}
|
||||||
|
_, managed, _, has, err := hostfile.ParseManagedBlock(lines)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
t.Fatal("expected HasManagedBlock=true for mixed markers")
|
||||||
|
}
|
||||||
|
if len(managed) != 1 {
|
||||||
|
t.Errorf("expected 1 managed line, got %v", managed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseManagedBlock_LegacyUpgradedOnWrite(t *testing.T) {
|
||||||
|
// Verify that a legacy block is parsed and then reassembled with current markers.
|
||||||
|
lines := []string{
|
||||||
|
"127.0.0.1 localhost",
|
||||||
|
hostfile.LegacyStartMarker,
|
||||||
|
"1.1.1.1\thost1",
|
||||||
|
hostfile.LegacyEndMarker,
|
||||||
|
"# trailing",
|
||||||
|
}
|
||||||
|
prefix, managed, postfix, _, err := hostfile.ParseManagedBlock(lines)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
result := hostfile.AssembleContent(prefix, managed, postfix)
|
||||||
|
// The reassembled output should use the current markers, not legacy.
|
||||||
|
found := false
|
||||||
|
for _, l := range result {
|
||||||
|
if l == hostfile.StartMarker {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
if l == hostfile.LegacyStartMarker {
|
||||||
|
t.Fatal("reassembled content should not contain legacy start marker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("reassembled content should contain current start marker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// AddEntries tests
|
// AddEntries tests
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -201,6 +329,52 @@ func TestAddEntries_ReplacesExistingHostname(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddEntries_DoesNotClobberSubdomains(t *testing.T) {
|
||||||
|
// Regression: substring matching would cause adding "example.com" to
|
||||||
|
// incorrectly remove "sub.example.com" entries.
|
||||||
|
existing := []string{
|
||||||
|
"1.1.1.1\tsub.example.com",
|
||||||
|
"2.2.2.2\tother.example.com",
|
||||||
|
}
|
||||||
|
entries := []hostfile.DNSEntry{{IP: "9.9.9.9", Hostname: "example.com"}}
|
||||||
|
result := hostfile.AddEntries(existing, entries)
|
||||||
|
if len(result) != 3 {
|
||||||
|
t.Fatalf("expected 3 entries (2 existing + 1 new), got %d: %v", len(result), result)
|
||||||
|
}
|
||||||
|
for _, line := range result {
|
||||||
|
if line == "1.1.1.1\tsub.example.com" || line == "2.2.2.2\tother.example.com" || line == "9.9.9.9\texample.com" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("unexpected line: %q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddEntries_MatchesAliasColumn(t *testing.T) {
|
||||||
|
// A hosts-file line can have aliases: "IP primary alias1 alias2"
|
||||||
|
// Replacing "alias1" should remove the entire line.
|
||||||
|
existing := []string{"1.1.1.1\tprimary\talias1"}
|
||||||
|
entries := []hostfile.DNSEntry{{IP: "9.9.9.9", Hostname: "alias1"}}
|
||||||
|
result := hostfile.AddEntries(existing, entries)
|
||||||
|
for _, line := range result {
|
||||||
|
if strings.Contains(line, "primary") {
|
||||||
|
t.Errorf("line with alias1 should have been removed, but found: %q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddEntries_CaseInsensitive(t *testing.T) {
|
||||||
|
// RFC 4343: hostnames are case-insensitive.
|
||||||
|
existing := []string{"1.1.1.1\tHost1.Example.COM"}
|
||||||
|
entries := []hostfile.DNSEntry{{IP: "9.9.9.9", Hostname: "host1.example.com"}}
|
||||||
|
result := hostfile.AddEntries(existing, entries)
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("expected 1 entry (old replaced), got %d: %v", len(result), result)
|
||||||
|
}
|
||||||
|
if result[0] != "9.9.9.9\thost1.example.com" {
|
||||||
|
t.Errorf("expected new entry, got %q", result[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddEntries_Deduplicates(t *testing.T) {
|
func TestAddEntries_Deduplicates(t *testing.T) {
|
||||||
existing := []string{"1.1.1.1\thost1"}
|
existing := []string{"1.1.1.1\thost1"}
|
||||||
entries := []hostfile.DNSEntry{
|
entries := []hostfile.DNSEntry{
|
||||||
@@ -332,6 +506,54 @@ func TestRemoveByHostname_RegressionNoDuplicates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveByHostname_DoesNotClobberSubdomains(t *testing.T) {
|
||||||
|
// Regression: substring matching would remove "sub.example.com" when
|
||||||
|
// only "example.com" was requested for removal.
|
||||||
|
existing := []string{
|
||||||
|
"1.1.1.1\texample.com",
|
||||||
|
"2.2.2.2\tsub.example.com",
|
||||||
|
"3.3.3.3\tmy-example.com",
|
||||||
|
}
|
||||||
|
result := hostfile.RemoveByHostname(existing, []string{"example.com"})
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Fatalf("expected 2 remaining entries, got %d: %v", len(result), result)
|
||||||
|
}
|
||||||
|
if result[0] != "2.2.2.2\tsub.example.com" {
|
||||||
|
t.Errorf("expected sub.example.com to remain, got %q", result[0])
|
||||||
|
}
|
||||||
|
if result[1] != "3.3.3.3\tmy-example.com" {
|
||||||
|
t.Errorf("expected my-example.com to remain, got %q", result[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveByHostname_MatchesAliasColumn(t *testing.T) {
|
||||||
|
existing := []string{
|
||||||
|
"1.1.1.1\tprimary\talias1",
|
||||||
|
"2.2.2.2\thost2",
|
||||||
|
}
|
||||||
|
result := hostfile.RemoveByHostname(existing, []string{"alias1"})
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("expected 1 remaining entry, got %d: %v", len(result), result)
|
||||||
|
}
|
||||||
|
if result[0] != "2.2.2.2\thost2" {
|
||||||
|
t.Errorf("expected host2 to remain, got %q", result[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveByHostname_CaseInsensitive(t *testing.T) {
|
||||||
|
existing := []string{
|
||||||
|
"1.1.1.1\tHost1.Example.COM",
|
||||||
|
"2.2.2.2\thost2",
|
||||||
|
}
|
||||||
|
result := hostfile.RemoveByHostname(existing, []string{"host1.example.com"})
|
||||||
|
if len(result) != 1 {
|
||||||
|
t.Fatalf("expected 1 remaining entry, got %d: %v", len(result), result)
|
||||||
|
}
|
||||||
|
if result[0] != "2.2.2.2\thost2" {
|
||||||
|
t.Errorf("expected host2 to remain, got %q", result[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// RemoveAll tests (T018)
|
// RemoveAll tests (T018)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ import (
|
|||||||
"ekdns/resolver"
|
"ekdns/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// version and copyrightYear are set at build time via -ldflags.
|
||||||
|
var version = "dev"
|
||||||
|
var copyrightYear = "2024"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Banner (always printed to stdout).
|
// Banner (always printed to stdout).
|
||||||
fmt.Println("ekDNSHelper v1.0")
|
fmt.Printf("ekDNSHelper %s\n", version)
|
||||||
fmt.Println("Copyright (c) 2024 Emberkom LLC")
|
fmt.Printf("Copyright (c) %s Emberkom LLC\n", copyrightYear)
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
|
|||||||
Reference in New Issue
Block a user