Skip to content

Commit 470ca58

Browse files
committed
v0.2.0 - complete rewrite
1 parent ecb4c56 commit 470ca58

File tree

9 files changed

+355
-88
lines changed

9 files changed

+355
-88
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
build:
2-
go build -o go-wsl-host.exe ./cmd/wsl2host
2+
go build -o wsl2host.exe ./cmd/wsl2host
33

44
.PHONY: build

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# go-wsl2-host
22

3-
> Take a look at https://github.com/shayne/wsl2-hacks for things like auto-starting services, running commands when the VM boots and making localhost ports accessible
3+
> v0.2.0 is out, this drops support for `windows.local`, if this was important let me know and I can add it back in.
44
5-
A workaround for accessing the WSL2 VM from the Windows host and vice versa.
5+
A workaround for accessing the WSL2 VM from the Windows host.
66

77
This program installs as a service and runs under the local user account. It automatically updates your Windows hosts file with the WSL2 VM's IP address.
88

9-
The program uses the hostname `wsl.local` for the WSL VM and `windows.local` for the Windows host.
9+
The program uses the name of your distro, modified to be a hostname. For example "Ubuntu-18.04" becomes `ubuntu1804.wsl`. If you have more than one running distro, it will be added as well. When the distro stops it is removed from the host file.
1010

1111
I wrote this for my own use but thought it might be useful for others. It's not perfect but gets the job done for me.
1212

@@ -15,11 +15,11 @@ To install and run, download a binary from the releases tab. Place it somewhere
1515
Open an **elevated/administrator** command prompt:
1616

1717
```
18-
> .\go-wsl2-host.exe install
18+
> .\wsl2host.exe install
1919
Windows Username: <username-you-use-to-login-to-windows>
2020
Windows Password: <password-for-this-user>
2121
```
2222

23-
The program will install a service and start it up. Launch `wsl` then from a `cmd` prompt, run `ping wsl.local`. You can check the Windows hosts file to see what was written. The service will automatically update the IP if the WSL2 VM is stopped and started again.
23+
The program will install a service and start it up. Launch `wsl` then from a `cmd` prompt, run `ping ubuntu1804.wsl`. You can check the Windows hosts file to see what was written. The service will automatically update the IP if the WSL2 VM is stopped and started again.
2424

2525
The Windows hosts file is located at: `C:\Windows\System32\drivers\etc\hosts`

cmd/wsl2host/internal/service.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,9 @@ loop:
3030
for {
3131
select {
3232
case <-tick:
33-
is, err := service.IsRunning()
33+
err := service.Run()
3434
if err != nil {
3535
elog.Error(1, fmt.Sprintf("%v", err))
36-
} else if is {
37-
err := service.UpdateIP()
38-
if err != nil {
39-
elog.Error(1, fmt.Sprintf("%v", err))
40-
}
4136
}
4237
case c := <-r:
4338
switch c.Cmd {
Lines changed: 41 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,68 @@
11
package service
22

33
import (
4-
"bufio"
54
"fmt"
6-
"os"
5+
"regexp"
76
"strings"
87

9-
"github.com/shayne/go-wsl2-host/pkg/wslcli"
8+
"github.com/shayne/go-wsl2-host/pkg/hostsapi"
9+
10+
"github.com/shayne/go-wsl2-host/pkg/wslapi"
1011
)
1112

12-
const wslHostname = "wsl.local"
13-
const windowsHostname = "windows.local"
13+
const tld = ".wsl"
1414

15-
// IsRunning returns whether or not WSL is running
16-
func IsRunning() (bool, error) {
17-
running, err := wslcli.Running()
18-
if err != nil {
19-
return false, err
20-
}
21-
return running, nil
22-
}
15+
var hostnamereg, _ = regexp.Compile("[^A-Za-z0-9]+")
2316

24-
func getWSLIP() (string, error) {
25-
ip, err := wslcli.GetWSLIP()
26-
if err != nil {
27-
return "", err
28-
}
29-
return strings.TrimSpace(ip), nil
17+
func distroNameToHostname(distroname string) string {
18+
// Ubuntu-18.04
19+
// => ubuntu1804.wsl
20+
hostname := strings.ToLower(distroname)
21+
hostname = hostnamereg.ReplaceAllString(hostname, "")
22+
return hostname + tld
3023
}
3124

32-
// UpdateIP updates the Windows hosts file
33-
func UpdateIP() error {
34-
wslIP, err := getWSLIP()
35-
if err != nil {
36-
return err
37-
}
38-
hostIP, err := wslcli.GetHostIP()
25+
// Run main entry point to service logic
26+
func Run() error {
27+
infos, err := wslapi.GetAllInfo()
3928
if err != nil {
40-
return err
29+
return fmt.Errorf("failed to get infos: %w", err)
4130
}
42-
f, err := os.OpenFile("c:/Windows/System32/drivers/etc/hosts", os.O_RDWR, 0600)
31+
32+
hapi, err := hostsapi.CreateAPI(tld)
4333
if err != nil {
44-
return err
34+
return fmt.Errorf("failed to create hosts api: %w", err)
4535
}
46-
defer f.Close()
4736

48-
wslExisted := false
49-
wslWasCorrect := false
50-
hostExisted := false
51-
hostWasCorrect := false
52-
scanner := bufio.NewScanner(f)
53-
lines := make([]string, 0, 50)
54-
55-
wslLine := fmt.Sprintf("%s %s", wslIP, wslHostname)
56-
hostLine := fmt.Sprintf("%s %s", hostIP, windowsHostname)
37+
hostentries := hapi.Entries()
38+
for _, i := range infos {
39+
hostname := distroNameToHostname(i.Name)
40+
// remove stopped distros
41+
if i.Running == false {
42+
hapi.RemoveEntry(hostname)
43+
continue
44+
}
5745

58-
for scanner.Scan() {
59-
line := scanner.Text()
60-
if strings.HasSuffix(line, wslHostname) {
61-
if strings.Contains(line, wslIP) {
62-
wslWasCorrect = true
63-
lines = append(lines, line)
64-
} else {
65-
wslExisted = true
66-
lines = append(lines, wslLine)
67-
}
68-
} else if strings.HasSuffix(line, windowsHostname) {
69-
if strings.Contains(line, hostIP) {
70-
hostWasCorrect = true
71-
lines = append(lines, line)
72-
} else {
73-
hostExisted = true
74-
lines = append(lines, hostLine)
46+
// update IPs of running distros
47+
ip, err := wslapi.GetIP(i.Name)
48+
if he, exists := hostentries[hostname]; exists {
49+
if err != nil {
50+
return fmt.Errorf("failed to get IP for distro %q: %w", i.Name, err)
7551
}
52+
he.IP = ip
7653
} else {
77-
lines = append(lines, line)
54+
// add running distros not present
55+
hapi.AddEntry(&hostsapi.HostEntry{
56+
Hostname: hostname,
57+
IP: ip,
58+
})
7859
}
7960
}
80-
if err := scanner.Err(); err != nil {
81-
return err
82-
}
8361

84-
if !wslWasCorrect && !wslExisted {
85-
lines = append(lines, wslLine)
86-
}
87-
if !hostWasCorrect && !hostExisted {
88-
lines = append(lines, hostLine)
89-
}
90-
91-
_, err = f.WriteAt([]byte(strings.Join(lines, "\r\n")), 0)
62+
err = hapi.Write()
9263
if err != nil {
93-
return err
64+
return fmt.Errorf("failed to write hosts file: %w", err)
9465
}
66+
9567
return nil
9668
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ go 1.12
55
require (
66
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
77
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
8+
golang.org/x/text v0.3.0
89
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
66
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
77
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
88
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
9+
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
910
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

pkg/hostsapi/hostsapi.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package hostsapi
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"errors"
7+
"fmt"
8+
"os"
9+
"strings"
10+
)
11+
12+
const hostspath = "C:/Windows/System32/drivers/etc/hosts"
13+
14+
// HostEntry data structure for IP and hostnames
15+
type HostEntry struct {
16+
Idx int
17+
IP string
18+
Hostname string
19+
}
20+
21+
// HostsAPI data structure
22+
type HostsAPI struct {
23+
filter string
24+
hostsfile *os.File
25+
entries map[string]*HostEntry
26+
remidxs map[int]interface{}
27+
}
28+
29+
func parseHostfileLine(idx int, line string) ([]*HostEntry, error) {
30+
if len(line) <= 0 {
31+
return nil, errors.New("invalid line")
32+
}
33+
line = strings.TrimSpace(line)
34+
if line[0] == '#' {
35+
return nil, errors.New("comment line")
36+
}
37+
fields := strings.Fields(line)
38+
var validfields []string
39+
for _, f := range fields {
40+
if len(f) <= 0 {
41+
continue
42+
}
43+
if f[0] == '#' { // inline comment
44+
break // don't process any more
45+
}
46+
validfields = append(validfields, f)
47+
}
48+
if len(validfields) <= 1 {
49+
return nil, fmt.Errorf("invalid fields for line: %q", line)
50+
}
51+
var entries []*HostEntry
52+
for _, hostname := range validfields[1:] {
53+
entries = append(entries, &HostEntry{
54+
Idx: idx,
55+
IP: validfields[0],
56+
Hostname: hostname,
57+
})
58+
}
59+
60+
return entries, nil
61+
}
62+
63+
func (h *HostsAPI) loadAndParse() error {
64+
scanner := bufio.NewScanner(h.hostsfile)
65+
idx := 0
66+
for scanner.Scan() {
67+
line := scanner.Text()
68+
entries, err := parseHostfileLine(idx, line)
69+
idx++
70+
if err != nil {
71+
// log.Println(err) // debug
72+
continue
73+
}
74+
for _, e := range entries {
75+
if h.filter == "" || strings.Contains(e.Hostname, h.filter) {
76+
h.entries[e.Hostname] = e
77+
h.remidxs[e.Idx] = nil
78+
}
79+
}
80+
}
81+
h.hostsfile.Seek(0, 0)
82+
return nil
83+
}
84+
85+
// CreateAPI creates a new instance of the hosts file API
86+
// Call Close() when finished
87+
// `filter` proves ability to filter by string contains
88+
func CreateAPI(filter string) (*HostsAPI, error) {
89+
f, err := os.Open(hostspath)
90+
if err != nil {
91+
return nil, fmt.Errorf("failed to open hosts file: %w", err)
92+
}
93+
h := &HostsAPI{
94+
filter: filter,
95+
remidxs: make(map[int]interface{}),
96+
entries: make(map[string]*HostEntry),
97+
hostsfile: f,
98+
}
99+
err = h.loadAndParse()
100+
if err != nil {
101+
return nil, fmt.Errorf("failed to parse hosts file: %w", err)
102+
}
103+
return h, nil
104+
}
105+
106+
// Close closes the hosts file
107+
func (h *HostsAPI) Close() error {
108+
err := h.hostsfile.Close()
109+
if err != nil {
110+
return fmt.Errorf("failed to close hosts file: %w", err)
111+
}
112+
113+
return nil
114+
}
115+
116+
// Entries returns parsed entries of host file
117+
func (h *HostsAPI) Entries() map[string]*HostEntry {
118+
return h.entries
119+
}
120+
121+
// RemoveEntry removes existing entry from hosts file
122+
func (h *HostsAPI) RemoveEntry(hostname string) error {
123+
if _, exists := h.entries[hostname]; exists {
124+
delete(h.entries, hostname)
125+
} else {
126+
return fmt.Errorf("failed to remove, hostname does not exist: %s", hostname)
127+
}
128+
return nil
129+
}
130+
131+
// AddEntry adds a new HostEntry
132+
func (h *HostsAPI) AddEntry(entry *HostEntry) error {
133+
if _, exists := h.entries[entry.Hostname]; exists {
134+
return fmt.Errorf("failed to add entry, hostname already exists: %s", entry.Hostname)
135+
}
136+
137+
h.entries[entry.Hostname] = entry
138+
139+
return nil
140+
}
141+
142+
// Write
143+
func (h *HostsAPI) Write() error {
144+
var outbuf bytes.Buffer
145+
146+
// first remove all current entries
147+
scanner := bufio.NewScanner(h.hostsfile)
148+
for idx := 0; scanner.Scan() == true; idx++ {
149+
line := scanner.Text()
150+
if _, exists := h.remidxs[idx]; !exists {
151+
outbuf.WriteString(line)
152+
outbuf.WriteString("\r\n")
153+
}
154+
}
155+
156+
// append entries to file
157+
for _, e := range h.entries {
158+
outbuf.WriteString(fmt.Sprintf("%s %s # managed by wsl2-host\r\n", e.IP, e.Hostname))
159+
}
160+
161+
fmt.Println(string(outbuf.Bytes()))
162+
f, err := os.OpenFile(hostspath, os.O_WRONLY|os.O_TRUNC, 0600)
163+
if err != nil {
164+
return fmt.Errorf("failed to open hosts file for writing: %w", err)
165+
}
166+
defer f.Close()
167+
168+
f.Write(outbuf.Bytes())
169+
170+
return nil
171+
}

0 commit comments

Comments
 (0)