Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localhost server access #68

Merged
merged 11 commits into from
Nov 25, 2024
8 changes: 4 additions & 4 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "1.23.3"
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
# version: latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "1.23.3"
cache: true
cache-dependency-path: src/go.sum
- name: Check GoReleaser Config
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
bin/*
!bin/.gitkeep
src/dist
*.exe

# macOS
.DS_Store
Expand Down
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ In this diagram, the Client has generated and installed WireGuard configuration
- [Features](#features)
- [Demo](#demo)
- [Experimental](#experimental)
- [Localhost Server Access](#localhost-server-access)
- [TCP Tunneling](#tcp-tunneling)
- [Add Clients To Any Server](#add-clients-to-any-server)

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

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.

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).
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).

# How it Works

Expand Down Expand Up @@ -539,6 +540,30 @@ Please see the [Demo page in the Wiki](https://github.com/sandialabs/wiretap/wik

# Experimental

## Localhost Server Access

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.

When running the `configure` or `add server` commands, you can specify a `--localhost-ip <IPv4 address>` argument. For example:
```bash
./wiretap configure --endpoint 7.3.3.1:1337 --routes 10.0.0.0/24 -i 192.168.137.137
```
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.

> [!CAUTION]
> 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.

Under the hood, this feature is roughly equivalent to adding this `iptables` rule to Wiretap's userspace networking stack on the Server:
```
iptables -t nat -A PREROUTING -p tcp -d <IPv4 address> -j DNAT --to-destination 127.0.0.1
```

Limitations:
- Currently this only works for TCP connections, and only for an IPv4 target address.
- 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.
- This feature does not provide access to other IPs in the 127.0.0.0/8 space.


## TCP Tunneling

> [!WARNING]
Expand Down
6 changes: 5 additions & 1 deletion src/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ type request struct {

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

r, err := http.NewRequest(req.Method, req.URL, reqBody)
Expand Down
25 changes: 16 additions & 9 deletions src/cmd/add_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type addServerCmdConfig struct {
writeToClipboard bool
port int
nickname string
localhostIP string
}

var addServerCmdArgs = addServerCmdConfig{
Expand All @@ -37,6 +38,7 @@ var addServerCmdArgs = addServerCmdConfig{
writeToClipboard: false,
port: USE_ENDPOINT_PORT,
nickname: "",
localhostIP: "",
}

// addServerCmd represents the server command.
Expand All @@ -56,8 +58,9 @@ func init() {
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")
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")
addServerCmd.Flags().StringVarP(&addServerCmdArgs.nickname, "nickname", "n", addServerCmdArgs.nickname, "Server nickname to display in 'status' command")
addServerCmd.Flags().StringVarP(&addServerCmdArgs.localhostIP, "localhost-ip", "i", addServerCmdArgs.localhostIP, "[EXPERIMENTAL] Redirect wiretap packets destined for this IPv4 address to server's localhost")
addServerCmd.Flags().BoolVarP(&addServerCmdArgs.writeToClipboard, "clipboard", "c", addServerCmdArgs.writeToClipboard, "copy configuration args to clipboard")

addServerCmd.Flags().StringVarP(&addServerCmdArgs.configFileRelay, "relay-input", "", addServerCmdArgs.configFileRelay, "filename of input relay config file")
addServerCmd.Flags().StringVarP(&addServerCmdArgs.configFileE2EE, "e2ee-input", "", addServerCmdArgs.configFileE2EE, "filename of input E2EE config file")
addServerCmd.Flags().StringVarP(&addServerCmdArgs.configFileServer, "server-output", "", addServerCmdArgs.configFileServer, "filename of server config output file")
Expand All @@ -67,7 +70,7 @@ func init() {

addServerCmd.Flags().SortFlags = false
addServerCmd.PersistentFlags().SortFlags = false

helpFunc := addCmd.HelpFunc()
addCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if !ShowHidden {
Expand All @@ -83,7 +86,7 @@ func init() {
} else {
fmt.Printf("Failed to hide flag %v: %v\n", f, err)
}

}
}
}
Expand Down Expand Up @@ -170,7 +173,7 @@ func (c addServerCmdConfig) Run() {
PublicKey: serverConfigE2EE.GetPublicKey(),
AllowedIPs: c.allowedIPs,
Endpoint: net.JoinHostPort(newRelayPrefixes[0].Addr().Next().Next().String(), fmt.Sprint(E2EEPort)),
Nickname: c.nickname,
Nickname: c.nickname,
})
check("failed to generate new e2ee peer", err)
clientConfigE2EE.AddPeer(serverE2EEPeer)
Expand Down Expand Up @@ -264,7 +267,7 @@ func (c addServerCmdConfig) Run() {
PublicKey: serverConfigE2EE.GetPublicKey(),
AllowedIPs: c.allowedIPs,
Endpoint: net.JoinHostPort(addresses.NextServerRelayAddr4.String(), fmt.Sprint(E2EEPort)),
Nickname: c.nickname,
Nickname: c.nickname,
})
check("failed to parse server as peer", err)
clientConfigE2EE.AddPeer(serverPeerConfigE2EE)
Expand Down Expand Up @@ -336,20 +339,24 @@ func (c addServerCmdConfig) Run() {
// Leaf server is the relay peer for the new server.
clientConfigRelay = leafServerConfigRelay
}

// Set port defaults
if c.port == USE_ENDPOINT_PORT {
if addArgs.outbound { //for outbound, default port is same as endpoint port
c.port = portFromEndpoint(addArgs.endpoint)

} else { //for inbound, use a reasonable default for server relay listening port
c.port = Port;
c.port = Port
}
}

err = serverConfigRelay.SetPort(c.port)
check("failed to set port", err)

// Setup localhost IP relay
err = serverConfigRelay.SetLocalhostIP(c.localhostIP)
check("failed to set localhost IP", err)

// Overwrite Relay file with new server peer if adding a server directly to the client.
var fileStatusRelay string
if len(c.serverAddress) == 0 {
Expand Down
46 changes: 28 additions & 18 deletions src/cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type configureCmdConfig struct {
keepalive int
mtu int
disableV6 bool
localhostIP string
}

// Defaults for configure command.
Expand All @@ -61,6 +62,7 @@ var configureCmdArgs = configureCmdConfig{
keepalive: Keepalive,
mtu: MTU,
disableV6: false,
localhostIP: "",
}

// configureCmd represents the configure command.
Expand All @@ -82,7 +84,8 @@ func init() {
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")
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.")
configureCmd.Flags().StringVarP(&configureCmdArgs.nickname, "nickname", "n", configureCmdArgs.nickname, "Server nickname to display in 'status' command")

configureCmd.Flags().StringVarP(&configureCmdArgs.localhostIP, "localhost-ip", "i", configureCmdArgs.localhostIP, "[EXPERIMENTAL] Redirect wiretap packets destined for this IPv4 address to server's localhost")

configureCmd.Flags().StringVarP(&configureCmdArgs.configFileRelay, "relay-output", "", configureCmdArgs.configFileRelay, "wireguard relay config output filename")
configureCmd.Flags().StringVarP(&configureCmdArgs.configFileE2EE, "e2ee-output", "", configureCmdArgs.configFileE2EE, "wireguard E2EE config output filename")
configureCmd.Flags().StringVarP(&configureCmdArgs.configFileServer, "server-output", "s", configureCmdArgs.configFileServer, "wiretap server config output filename")
Expand All @@ -93,7 +96,7 @@ func init() {
configureCmd.Flags().IntVarP(&configureCmdArgs.keepalive, "keepalive", "k", configureCmdArgs.keepalive, "tunnel keepalive in seconds, only applies to outbound handshakes")
configureCmd.Flags().IntVarP(&configureCmdArgs.mtu, "mtu", "m", configureCmdArgs.mtu, "tunnel MTU")
configureCmd.Flags().BoolVarP(&configureCmdArgs.disableV6, "disable-ipv6", "", configureCmdArgs.disableV6, "disables IPv6")

configureCmd.Flags().StringVarP(&configureCmdArgs.clientAddr4Relay, "ipv4-relay", "", configureCmdArgs.clientAddr4Relay, "ipv4 relay address")
configureCmd.Flags().StringVarP(&configureCmdArgs.clientAddr6Relay, "ipv6-relay", "", configureCmdArgs.clientAddr6Relay, "ipv6 relay address")
configureCmd.Flags().StringVarP(&configureCmdArgs.clientAddr4E2EE, "ipv4-e2ee", "", configureCmdArgs.clientAddr4E2EE, "ipv4 e2ee address")
Expand All @@ -112,15 +115,15 @@ func init() {
configureCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
if !ShowHidden {
for _, f := range []string{
"api",
"ipv4-relay",
"ipv6-relay",
"ipv4-e2ee",
"ipv6-e2ee",
"ipv4-relay-server",
"ipv6-relay-server",
"keepalive",
"mtu",
"api",
"ipv4-relay",
"ipv6-relay",
"ipv4-e2ee",
"ipv6-e2ee",
"ipv4-relay-server",
"ipv6-relay-server",
"keepalive",
"mtu",
"disable-ipv6",
"relay-output",
"e2ee-output",
Expand All @@ -141,6 +144,9 @@ func init() {
func (c configureCmdConfig) Run() {
var err error

if c.localhostIP != "" {
c.allowedIPs = append(c.allowedIPs, c.localhostIP+"/32")
}
if c.disableV6 && netip.MustParsePrefix(c.apiAddr).Addr().Is6() {
c.apiAddr = c.apiv4Addr
}
Expand Down Expand Up @@ -177,24 +183,24 @@ func (c configureCmdConfig) Run() {
if !c.disableV6 {
clientE2EEAddrs = append(clientE2EEAddrs, c.clientAddr6E2EE)
}

if c.port == USE_ENDPOINT_PORT {
c.port = portFromEndpoint(c.endpoint)
}

// We only configure one of these (based on --outbound or not)
// The other must be manually changed in the configs/command/envs
var clientPort int;
var serverPort int;
var clientPort int
var serverPort int

if c.outbound {
clientPort = Port
serverPort = c.port
} else {
clientPort = c.port
serverPort = Port
}

err = serverConfigRelay.SetPort(serverPort)
check("failed to set port", err)

Expand Down Expand Up @@ -242,7 +248,7 @@ func (c configureCmdConfig) Run() {
PublicKey: serverConfigE2EE.GetPublicKey(),
AllowedIPs: c.allowedIPs,
Endpoint: net.JoinHostPort(relaySubnet4.Addr().Next().Next().String(), fmt.Sprint(E2EEPort)),
Nickname: c.nickname,
Nickname: c.nickname,
},
},
Addresses: clientE2EEAddrs,
Expand Down Expand Up @@ -277,6 +283,10 @@ func (c configureCmdConfig) Run() {
err = serverConfigRelay.SetMTU(c.mtu)
check("failed to set mtu", err)
}
if c.localhostIP != "" {
err = serverConfigRelay.SetLocalhostIP(c.localhostIP)
check("failed to set localhost IP", err)
}

// Add number to filename if it already exists.
c.configFileRelay = peer.FindAvailableFilename(c.configFileRelay)
Expand Down
Loading
Loading