Skip to content

Commit 21e6aba

Browse files
authored
Localhost server access (#68)
Add ability to access the 127.0.0.1 IP of a Server
1 parent fdfe186 commit 21e6aba

File tree

12 files changed

+222
-57
lines changed

12 files changed

+222
-57
lines changed

.github/workflows/golangci-lint.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ jobs:
1515
name: lint
1616
runs-on: ubuntu-latest
1717
steps:
18-
- uses: actions/checkout@v3
19-
- uses: actions/setup-go@v3
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-go@v5
2020
with:
21-
go-version: "1.20"
21+
go-version: "1.23.3"
2222
- name: golangci-lint
23-
uses: golangci/golangci-lint-action@v3
23+
uses: golangci/golangci-lint-action@v6
2424
with:
2525
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
2626
# version: latest

.github/workflows/goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Set up Go
2222
uses: actions/setup-go@v5
2323
with:
24-
go-version: "1.20"
24+
go-version: "1.23.3"
2525
cache: true
2626
cache-dependency-path: src/go.sum
2727
- name: Check GoReleaser Config

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
bin/*
77
!bin/.gitkeep
88
src/dist
9+
*.exe
910

1011
# macOS
1112
.DS_Store

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ In this diagram, the Client has generated and installed WireGuard configuration
3636
- [Features](#features)
3737
- [Demo](#demo)
3838
- [Experimental](#experimental)
39+
- [Localhost Server Access](#localhost-server-access)
3940
- [TCP Tunneling](#tcp-tunneling)
4041
- [Add Clients To Any Server](#add-clients-to-any-server)
4142

@@ -85,7 +86,7 @@ See the [Usage section](#Usage) for more details.
8586

8687
No installation of Wiretap is required. Just grab a binary from the [releases](https://github.com/sandialabs/wiretap/releases) page. You may need two different binaries if the OS/ARCH are different on the client and server machines.
8788

88-
If you want to compile it yourself or can't find the OS/ARCH you're looking for, install Go (>=1.20) from https://go.dev/dl/ and use the provided [Makefile](./src/Makefile).
89+
If you want to compile it yourself or can't find the OS/ARCH you're looking for, install Go (>=1.23.3) from https://go.dev/dl/ and use the provided [Makefile](./src/Makefile).
8990

9091
# How it Works
9192

@@ -539,6 +540,30 @@ Please see the [Demo page in the Wiki](https://github.com/sandialabs/wiretap/wik
539540
540541
# Experimental
541542

543+
## Localhost Server Access
544+
545+
Sometimes you want to access many ports on the Server itself that are listening on the localhost/loopback interface instead of a public interface. Rather than setting up many individual port forwards, you can use Wiretap's "localhost IP" redirection feature.
546+
547+
When running the `configure` or `add server` commands, you can specify a `--localhost-ip <IPv4 address>` argument. For example:
548+
```bash
549+
./wiretap configure --endpoint 7.3.3.1:1337 --routes 10.0.0.0/24 -i 192.168.137.137
550+
```
551+
Any packets received by this Server through the Wiretap network with this target destination address (`192.168.137.137` in this example) will be rerouted to the Server host's `127.0.0.1` loopback address instead, with replies routed back to the Client appropriately.
552+
553+
> [!CAUTION]
554+
> It is **strongly** recommended that you specify a private (non-routable) IP address to use for this option, preferably one that you know is not in use in the target network. This feature has only been lightly tested, so if the re-routing fails unexpectedly you want to ensure your traffic will go to a "safe" destination. For similar reasons you should not specify a broadcast address, or IPs that your Client already has routes for.
555+
556+
Under the hood, this feature is roughly equivalent to adding this `iptables` rule to Wiretap's userspace networking stack on the Server:
557+
```
558+
iptables -t nat -A PREROUTING -p tcp -d <IPv4 address> -j DNAT --to-destination 127.0.0.1
559+
```
560+
561+
Limitations:
562+
- Currently this only works for TCP connections, and only for an IPv4 target address.
563+
- Unfortunately there's [not a clean way](https://serverfault.com/a/975890) to do NAT to the IPv6 `::1` loopback address, so this feature can't be used to access services listening exclusively on that IPv6 address.
564+
- This feature does not provide access to other IPs in the 127.0.0.0/8 space.
565+
566+
542567
## TCP Tunneling
543568

544569
> [!WARNING]

src/api/api.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ type request struct {
2828

2929
// MakeRequest attempts to send an API query to the Wiretap server.
3030
func makeRequest(req request) ([]byte, error) {
31-
client := &http.Client{Timeout: 3 * time.Second}
31+
// Never use a proxy for API requests, they must go direct through the Wiretap network
32+
tr := &http.Transport{
33+
Proxy: nil,
34+
}
35+
client := &http.Client{Timeout: 3 * time.Second, Transport: tr}
3236
reqBody := bytes.NewBuffer(req.Body)
3337

3438
r, err := http.NewRequest(req.Method, req.URL, reqBody)

src/cmd/add_server.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type addServerCmdConfig struct {
2626
writeToClipboard bool
2727
port int
2828
nickname string
29+
localhostIP string
2930
}
3031

3132
var addServerCmdArgs = addServerCmdConfig{
@@ -37,6 +38,7 @@ var addServerCmdArgs = addServerCmdConfig{
3738
writeToClipboard: false,
3839
port: USE_ENDPOINT_PORT,
3940
nickname: "",
41+
localhostIP: "",
4042
}
4143

4244
// addServerCmd represents the server command.
@@ -56,8 +58,9 @@ func init() {
5658
addServerCmd.Flags().StringVarP(&addServerCmdArgs.serverAddress, "server-address", "s", addServerCmdArgs.serverAddress, "API address of server that new server will connect to, connects to client by default")
5759
addServerCmd.Flags().IntVarP(&addServerCmdArgs.port, "port", "p", addServerCmdArgs.port, "listener port to start on new server for wireguard relay. If --outbound, default is the port specified in --endpoint; otherwise default is 51820")
5860
addServerCmd.Flags().StringVarP(&addServerCmdArgs.nickname, "nickname", "n", addServerCmdArgs.nickname, "Server nickname to display in 'status' command")
61+
addServerCmd.Flags().StringVarP(&addServerCmdArgs.localhostIP, "localhost-ip", "i", addServerCmdArgs.localhostIP, "[EXPERIMENTAL] Redirect wiretap packets destined for this IPv4 address to server's localhost")
5962
addServerCmd.Flags().BoolVarP(&addServerCmdArgs.writeToClipboard, "clipboard", "c", addServerCmdArgs.writeToClipboard, "copy configuration args to clipboard")
60-
63+
6164
addServerCmd.Flags().StringVarP(&addServerCmdArgs.configFileRelay, "relay-input", "", addServerCmdArgs.configFileRelay, "filename of input relay config file")
6265
addServerCmd.Flags().StringVarP(&addServerCmdArgs.configFileE2EE, "e2ee-input", "", addServerCmdArgs.configFileE2EE, "filename of input E2EE config file")
6366
addServerCmd.Flags().StringVarP(&addServerCmdArgs.configFileServer, "server-output", "", addServerCmdArgs.configFileServer, "filename of server config output file")
@@ -67,7 +70,7 @@ func init() {
6770

6871
addServerCmd.Flags().SortFlags = false
6972
addServerCmd.PersistentFlags().SortFlags = false
70-
73+
7174
helpFunc := addCmd.HelpFunc()
7275
addCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
7376
if !ShowHidden {
@@ -83,7 +86,7 @@ func init() {
8386
} else {
8487
fmt.Printf("Failed to hide flag %v: %v\n", f, err)
8588
}
86-
89+
8790
}
8891
}
8992
}
@@ -170,7 +173,7 @@ func (c addServerCmdConfig) Run() {
170173
PublicKey: serverConfigE2EE.GetPublicKey(),
171174
AllowedIPs: c.allowedIPs,
172175
Endpoint: net.JoinHostPort(newRelayPrefixes[0].Addr().Next().Next().String(), fmt.Sprint(E2EEPort)),
173-
Nickname: c.nickname,
176+
Nickname: c.nickname,
174177
})
175178
check("failed to generate new e2ee peer", err)
176179
clientConfigE2EE.AddPeer(serverE2EEPeer)
@@ -264,7 +267,7 @@ func (c addServerCmdConfig) Run() {
264267
PublicKey: serverConfigE2EE.GetPublicKey(),
265268
AllowedIPs: c.allowedIPs,
266269
Endpoint: net.JoinHostPort(addresses.NextServerRelayAddr4.String(), fmt.Sprint(E2EEPort)),
267-
Nickname: c.nickname,
270+
Nickname: c.nickname,
268271
})
269272
check("failed to parse server as peer", err)
270273
clientConfigE2EE.AddPeer(serverPeerConfigE2EE)
@@ -336,20 +339,24 @@ func (c addServerCmdConfig) Run() {
336339
// Leaf server is the relay peer for the new server.
337340
clientConfigRelay = leafServerConfigRelay
338341
}
339-
342+
340343
// Set port defaults
341344
if c.port == USE_ENDPOINT_PORT {
342345
if addArgs.outbound { //for outbound, default port is same as endpoint port
343346
c.port = portFromEndpoint(addArgs.endpoint)
344-
347+
345348
} else { //for inbound, use a reasonable default for server relay listening port
346-
c.port = Port;
349+
c.port = Port
347350
}
348351
}
349-
352+
350353
err = serverConfigRelay.SetPort(c.port)
351354
check("failed to set port", err)
352355

356+
// Setup localhost IP relay
357+
err = serverConfigRelay.SetLocalhostIP(c.localhostIP)
358+
check("failed to set localhost IP", err)
359+
353360
// Overwrite Relay file with new server peer if adding a server directly to the client.
354361
var fileStatusRelay string
355362
if len(c.serverAddress) == 0 {

src/cmd/configure.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type configureCmdConfig struct {
3535
keepalive int
3636
mtu int
3737
disableV6 bool
38+
localhostIP string
3839
}
3940

4041
// Defaults for configure command.
@@ -61,6 +62,7 @@ var configureCmdArgs = configureCmdConfig{
6162
keepalive: Keepalive,
6263
mtu: MTU,
6364
disableV6: false,
65+
localhostIP: "",
6466
}
6567

6668
// configureCmd represents the configure command.
@@ -82,7 +84,8 @@ func init() {
8284
configureCmd.Flags().BoolVar(&configureCmdArgs.outbound, "outbound", configureCmdArgs.outbound, "client will initiate handshake to server; --endpoint now specifies server's listening socket instead of client's, and --port assigns the server's listening port instead of client's")
8385
configureCmd.Flags().IntVarP(&configureCmdArgs.port, "port", "p", configureCmdArgs.port, "listener port for wireguard relay. Default is to copy the --endpoint port. If --outbound, sets port for the server; else for the client.")
8486
configureCmd.Flags().StringVarP(&configureCmdArgs.nickname, "nickname", "n", configureCmdArgs.nickname, "Server nickname to display in 'status' command")
85-
87+
configureCmd.Flags().StringVarP(&configureCmdArgs.localhostIP, "localhost-ip", "i", configureCmdArgs.localhostIP, "[EXPERIMENTAL] Redirect wiretap packets destined for this IPv4 address to server's localhost")
88+
8689
configureCmd.Flags().StringVarP(&configureCmdArgs.configFileRelay, "relay-output", "", configureCmdArgs.configFileRelay, "wireguard relay config output filename")
8790
configureCmd.Flags().StringVarP(&configureCmdArgs.configFileE2EE, "e2ee-output", "", configureCmdArgs.configFileE2EE, "wireguard E2EE config output filename")
8891
configureCmd.Flags().StringVarP(&configureCmdArgs.configFileServer, "server-output", "s", configureCmdArgs.configFileServer, "wiretap server config output filename")
@@ -93,7 +96,7 @@ func init() {
9396
configureCmd.Flags().IntVarP(&configureCmdArgs.keepalive, "keepalive", "k", configureCmdArgs.keepalive, "tunnel keepalive in seconds, only applies to outbound handshakes")
9497
configureCmd.Flags().IntVarP(&configureCmdArgs.mtu, "mtu", "m", configureCmdArgs.mtu, "tunnel MTU")
9598
configureCmd.Flags().BoolVarP(&configureCmdArgs.disableV6, "disable-ipv6", "", configureCmdArgs.disableV6, "disables IPv6")
96-
99+
97100
configureCmd.Flags().StringVarP(&configureCmdArgs.clientAddr4Relay, "ipv4-relay", "", configureCmdArgs.clientAddr4Relay, "ipv4 relay address")
98101
configureCmd.Flags().StringVarP(&configureCmdArgs.clientAddr6Relay, "ipv6-relay", "", configureCmdArgs.clientAddr6Relay, "ipv6 relay address")
99102
configureCmd.Flags().StringVarP(&configureCmdArgs.clientAddr4E2EE, "ipv4-e2ee", "", configureCmdArgs.clientAddr4E2EE, "ipv4 e2ee address")
@@ -112,15 +115,15 @@ func init() {
112115
configureCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
113116
if !ShowHidden {
114117
for _, f := range []string{
115-
"api",
116-
"ipv4-relay",
117-
"ipv6-relay",
118-
"ipv4-e2ee",
119-
"ipv6-e2ee",
120-
"ipv4-relay-server",
121-
"ipv6-relay-server",
122-
"keepalive",
123-
"mtu",
118+
"api",
119+
"ipv4-relay",
120+
"ipv6-relay",
121+
"ipv4-e2ee",
122+
"ipv6-e2ee",
123+
"ipv4-relay-server",
124+
"ipv6-relay-server",
125+
"keepalive",
126+
"mtu",
124127
"disable-ipv6",
125128
"relay-output",
126129
"e2ee-output",
@@ -141,6 +144,9 @@ func init() {
141144
func (c configureCmdConfig) Run() {
142145
var err error
143146

147+
if c.localhostIP != "" {
148+
c.allowedIPs = append(c.allowedIPs, c.localhostIP+"/32")
149+
}
144150
if c.disableV6 && netip.MustParsePrefix(c.apiAddr).Addr().Is6() {
145151
c.apiAddr = c.apiv4Addr
146152
}
@@ -177,24 +183,24 @@ func (c configureCmdConfig) Run() {
177183
if !c.disableV6 {
178184
clientE2EEAddrs = append(clientE2EEAddrs, c.clientAddr6E2EE)
179185
}
180-
186+
181187
if c.port == USE_ENDPOINT_PORT {
182188
c.port = portFromEndpoint(c.endpoint)
183189
}
184-
190+
185191
// We only configure one of these (based on --outbound or not)
186192
// The other must be manually changed in the configs/command/envs
187-
var clientPort int;
188-
var serverPort int;
189-
193+
var clientPort int
194+
var serverPort int
195+
190196
if c.outbound {
191197
clientPort = Port
192198
serverPort = c.port
193199
} else {
194200
clientPort = c.port
195201
serverPort = Port
196202
}
197-
203+
198204
err = serverConfigRelay.SetPort(serverPort)
199205
check("failed to set port", err)
200206

@@ -242,7 +248,7 @@ func (c configureCmdConfig) Run() {
242248
PublicKey: serverConfigE2EE.GetPublicKey(),
243249
AllowedIPs: c.allowedIPs,
244250
Endpoint: net.JoinHostPort(relaySubnet4.Addr().Next().Next().String(), fmt.Sprint(E2EEPort)),
245-
Nickname: c.nickname,
251+
Nickname: c.nickname,
246252
},
247253
},
248254
Addresses: clientE2EEAddrs,
@@ -277,6 +283,10 @@ func (c configureCmdConfig) Run() {
277283
err = serverConfigRelay.SetMTU(c.mtu)
278284
check("failed to set mtu", err)
279285
}
286+
if c.localhostIP != "" {
287+
err = serverConfigRelay.SetLocalhostIP(c.localhostIP)
288+
check("failed to set localhost IP", err)
289+
}
280290

281291
// Add number to filename if it already exists.
282292
c.configFileRelay = peer.FindAvailableFilename(c.configFileRelay)

0 commit comments

Comments
 (0)