diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/tcpip/sample/wg_tunnel/README | 11 | ||||
-rw-r--r-- | pkg/tcpip/sample/wg_tunnel/gtun.go | 135 | ||||
-rw-r--r-- | pkg/tcpip/sample/wg_tunnel/main.go | 1025 | ||||
-rwxr-xr-x | pkg/tcpip/sample/wg_tunnel/run.sh | 34 | ||||
-rwxr-xr-x | pkg/tcpip/sample/wg_tunnel/setup.sh | 26 |
5 files changed, 1231 insertions, 0 deletions
diff --git a/pkg/tcpip/sample/wg_tunnel/README b/pkg/tcpip/sample/wg_tunnel/README new file mode 100644 index 000000000..ee3fd9f8a --- /dev/null +++ b/pkg/tcpip/sample/wg_tunnel/README @@ -0,0 +1,11 @@ +/go/bin/go mod edit -replace="github.com/insomniacslk/dhcp@v0.0.0=golang.m7n.se/insomniacslk-dhcp@v0.0.0" + + +sudo ip tuntap add tun1 mode tun +sudo ip l set up tun1 +sudo ip a a 10.1.1.1/24 dev tun1 +sudo ip r a 10.1.2.0/24 dev tun1 +ping 10.1.2.1 +ping 10.1.2.2 + +./wg_tunnel 10003 diff --git a/pkg/tcpip/sample/wg_tunnel/gtun.go b/pkg/tcpip/sample/wg_tunnel/gtun.go new file mode 100644 index 000000000..d641fc42d --- /dev/null +++ b/pkg/tcpip/sample/wg_tunnel/gtun.go @@ -0,0 +1,135 @@ +package main + +import ( + "context" + "fmt" + "os" + + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/buffer" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/stack" + wgtun "golang.zx2c4.com/wireguard/tun" +) + +type GoTun struct { + events chan wgtun.Event + ch *channel.Endpoint + stack *stack.Stack + ctx context.Context + cancel context.CancelFunc +} + +func (tun *GoTun) File() *os.File { + fmt.Println("File") + return nil +} + +func (tun *GoTun) Read(buff []byte, offset int) (int, error) { + fmt.Println("Read ", len(buff), offset) + + p, ok := tun.ch.ReadContext(tun.ctx) + if ok == false { + fmt.Println("Read error") + return 0, nil // FIXME error + } + vv := p.Pkt.Data + v := vv.ToView() + h := p.Pkt.Header.View() + fmt.Println("Read packet", vv.Size(), len(v), len(h), h) + + if len(buff) - offset < len(h) + len(v) { + fmt.Println("Short buffer") + return 0, nil // FIXME error + } + + copy(buff[offset:], h) + copy(buff[offset+len(h):], v) + return len(h)+len(v), nil +} + +func versionToProtocol(version int) tcpip.NetworkProtocolNumber { + switch version { + case header.IPv4Version: return header.IPv4ProtocolNumber + case header.IPv6Version: return header.IPv6ProtocolNumber + } + + return 0 +} + +func (tun *GoTun) Write(buff []byte, offset int) (int, error) { + size := len(buff) - offset + fmt.Println("Write ", len(buff), offset, size) + + if size < 1 { + return 0, nil // FIXME error + } + + buffSlice := buff[offset : offset+size] + + pkt := tcpip.PacketBuffer{ + Data: buffer.NewViewFromBytes(buffSlice).ToVectorisedView(), + } + //version := buff[offset] & 0x0f + protocol := versionToProtocol(header.IPVersion(buffSlice)) + netProto := tun.stack.NetworkProtocolInstance(protocol) + if netProto == nil { + fmt.Println("Write not ok") + return 0, nil + } + src, dst := netProto.ParseAddresses(pkt.Data.First()) + fmt.Println("Write ", src, dst) + // TODO change destination address + tun.ch.InjectInbound(protocol, pkt) // FIXME detect protocol number + + return size, nil +} + +func (tun *GoTun) Flush() error { + // TODO: can flushing be implemented by buffering and using sendmmsg? + fmt.Println("Flush") + return nil +} + +func (tun *GoTun) MTU() (int, error) { + fmt.Println("MTU") + return 1280, nil +} + +func (tun *GoTun) Name() (string, error) { + fmt.Println("Name") + return "foobar", nil +} + +func (tun *GoTun) Events() chan wgtun.Event { + fmt.Println("Events") + return tun.events +} + +func (tun *GoTun) Close() error { + fmt.Println("Close") + // TODO +// tun.cancel() + return nil +} + +func CreateGoTun(s *stack.Stack, ch *channel.Endpoint) (wgtun.Device, error) { + size := 16 + ctx, cancel := context.WithCancel(context.Background()) + tun := &GoTun{ + ch: ch, + events: make(chan wgtun.Event, size), + stack: s, + ctx: ctx, + cancel: cancel, + } + +// go func() { + fmt.Println("Post event") + tun.events <- wgtun.EventUp + fmt.Println("Posted event") +// }() + + return tun, nil +} diff --git a/pkg/tcpip/sample/wg_tunnel/main.go b/pkg/tcpip/sample/wg_tunnel/main.go new file mode 100644 index 000000000..e11eee227 --- /dev/null +++ b/pkg/tcpip/sample/wg_tunnel/main.go @@ -0,0 +1,1025 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +// This sample creates a stack with TCP and IPv4 protocols on top of a TUN +// device, and listens on a port. Data received by the server in the accepted +// connections is echoed back to the clients. +package main + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/hex" + "flag" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net" + "os" + "runtime" + "sort" + "strconv" + "strings" + "time" + + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" + "gvisor.dev/gvisor/pkg/tcpip/link/loopback" + "gvisor.dev/gvisor/pkg/tcpip/link/rawfile" + "gvisor.dev/gvisor/pkg/tcpip/link/tun" + "gvisor.dev/gvisor/pkg/tcpip/network/arp" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" + "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" + "gvisor.dev/gvisor/pkg/tcpip/transport/udp" + "gvisor.dev/gvisor/pkg/waiter" + + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/ipc" +// wg_tun "golang.zx2c4.com/wireguard/tun" + + "gopkg.in/yaml.v3" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/insomniacslk/dhcp/dhcpv6/nclient6" + "github.com/insomniacslk/dhcp/iana" +) + +type Route struct { + To string `yaml:"to"` + Via string `yaml:"via"` + Metric int `yaml:"metric"` + Mark uint32 `yaml:"mask"` + Markmask uint32 `yaml:"markmask"` +} + +type Common struct { + Match struct { + Name string `yaml:"name"` + } `yaml:"match"` + Addresses []string `yaml:"addresses"` + Nameservers struct { + Addresses []string `yaml:"addresses"` + } `yaml:"nameservers"` + Macaddress string `yaml:"macaddress"` + Routes []Route `yaml:"routes"` + Mtu uint32 `yaml:"mtu"` +} + +type Ethernet struct { + Common `yaml:",inline"` +} + +type Tuntap struct { + Common `yaml:",inline"` + Mode string `yaml:"mode"` + Name string `yaml:"name"` +} + +type WireguardKey []byte + +func (wgKey *WireguardKey) UnmarshalYAML(value *yaml.Node) error{ + key, err := base64.StdEncoding.DecodeString(value.Value) + fmt.Println("UnmarshalYAML", key, err) + *wgKey = key + return err +} + +type WireguardPeer struct { + PublicKey WireguardKey `yaml:"public_key"` + Endpoint string `yaml:"endpoint"` + AllowedIPs []string `yaml:"allowed_ips"` + PersistentKeepalive int `yaml:"persistent_keepalive"` +} + +func (peer WireguardPeer) String() string{ + return fmt.Sprintf("{PublicKey=%v, Endpoint=%v, AllowedIPs=%v, PersistentKeepalive=%v}", peer.PublicKey, peer.Endpoint, peer.AllowedIPs, peer.PersistentKeepalive) +} + +type Wireguard struct { + Common `yaml:",inline"` + Name string `yaml:"name"` + ListenPort uint16 `yaml:"listen_port"` + PrivateKey WireguardKey `yaml:"private_key"` + Peers []*WireguardPeer `yaml:"peers"` +} + +type Tunnel struct { + Common `yaml:",inline"` + Mode string `yaml:"mode"` + Local string `yaml:"local"` + Remote string `yaml:"remote"` + + Conn *net.UDPConn + Sd *os.File +} + +type Netplan struct { + Network struct { + Version int `yaml:"version"` + Renderer string `yaml:"renderer"` + Ethernets map[string] *Ethernet `yaml:"ethernets"` + Tuntaps map[string] *Tuntap `yaml:"tuntaps"` + Wireguards map[string] *Wireguard `yaml:"wireguards"` + Tunnels map[string] *Tunnel `yaml:"tunnels"` + } `yaml:"network"` +} + +func echo(wq *waiter.Queue, ep tcpip.Endpoint) { + defer ep.Close() + + // Create wait queue entry that notifies a channel. + waitEntry, notifyCh := waiter.NewChannelEntry(nil) + + wq.EventRegister(&waitEntry, waiter.EventIn) + defer wq.EventUnregister(&waitEntry) + + for { + v, _, err := ep.Read(nil) + if err != nil { + if err == tcpip.ErrWouldBlock { + <-notifyCh + continue + } + + return + } + + ep.Write(tcpip.SlicePayload(v), tcpip.WriteOptions{}) + } +} + +func addTunLink(s *stack.Stack, nic tcpip.NICID, id string, tap bool, addr tcpip.LinkAddress, tuntap *Tuntap) { + var err error + + mtu := tuntap.Mtu + tunName := tuntap.Name + + if mtu == 0 { + mtu, err = rawfile.GetMTU(tunName) + if err != nil { + log.Fatal("GetMTU", err) + } + } + + var fd int + if tap { + fd, err = tun.OpenTAP(tunName) + } else { + fd, err = tun.Open(tunName) + } + if err != nil { + log.Fatalf("Open %s %b %s", err, tap, tunName) + } + + linkEP, err := fdbased.New(&fdbased.Options{ + FDs: []int{fd}, + MTU: mtu, + EthernetHeader: tap, + Address: addr, + }) + if err := s.CreateNICWithOptions(nic, linkEP, stack.NICOptions{Name: id, Disabled: true}); err != nil { + log.Fatal("CreateNIC", err) + } + + if tap { + if err := s.AddAddress(nic, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { + log.Fatal("AddAddress", err) + } + } +} + +func CheckError(err error) { + if err != nil { + log.Fatal("Error: " , err) + } +} + +func TestFinialize(conn *net.UDPConn) { + event := make(chan string) + + runtime.SetFinalizer(conn, func (obj *net.UDPConn) { + fmt.Println("Finalize: ", obj.LocalAddr(), obj.RemoteAddr()) + event <- "Finalize" + }) + + runtime.GC() + + select { + case res := <-event: + fmt.Println(res) + case <-time.After(2 * time.Second): + fmt.Println("No finalize") + } +} + +func addRouterLink(s *stack.Stack, nic tcpip.NICID, id string, addr tcpip.LinkAddress, + tun *Tunnel) { + + ServerAddr,err := net.ResolveUDPAddr("udp", tun.Remote) + CheckError(err) + + LocalAddr, err := net.ResolveUDPAddr("udp", tun.Local) + CheckError(err) + + conn, err := net.DialUDP("udp", LocalAddr, ServerAddr) + CheckError(err) + + tun.Conn = conn + + fmt.Println("Tunnel ", conn) + + sd, err := conn.File() + CheckError(err) + + tun.Sd = sd + + conn.Close() + + var fd int + fd = int(sd.Fd()) + //TestFinialize(&tun.Conn) + runtime.GC() + linkEP, err := fdbased.New(&fdbased.Options{ + FDs: []int{fd}, + MTU: tun.Mtu, + EthernetHeader: true, +// EthernetHeader: false, + Address: addr, + }) + CheckError(err) + + fmt.Println("addRouterLink MTU ", tun.Mtu, tun.Conn) + //channelSize := 128 + // linkEP := channel.New(channelSize, mtu, addr) + if err := s.CreateNICWithOptions(nic, linkEP, stack.NICOptions{Name: id, Disabled: true}); err != nil { + log.Fatal("CreateNIC ", id, err) + } + + if err := s.AddAddress(nic, arp.ProtocolNumber, arp.ProtocolAddress); err != nil { + log.Fatal("AddAddress", err) + } + +// client(linkEP.C) + fmt.Println("Tunnel ", tun.Conn) +} + +func addWgLink(s *stack.Stack, nic tcpip.NICID, interfaceName string, addr tcpip.LinkAddress) *device.Device { + loglevel := device.LogLevelDebug + + chanSize := 1024 + var chanMtu uint32 = 1420 + ep := channel.New(chanSize, chanMtu, addr) + + logger := device.NewLogger(loglevel, "(wg_tunnel) ") + + //mtu := 1500 + // tun, err := wg_tun.CreateTUN(interfaceName, mtu) + tun, err := CreateGoTun(s, ep) + if err != nil { + log.Fatal("CreateGoTun", err) + } + + + fileUAPI, err := func() (*os.File, error) { + ENV_WG_UAPI_FD := "WG_UAPI_FD" + uapiFdStr := os.Getenv(ENV_WG_UAPI_FD) + if uapiFdStr == "" { + return ipc.UAPIOpen(interfaceName) + } + + // use supplied fd + + fd, err := strconv.ParseUint(uapiFdStr, 10, 32) + if err != nil { + return nil, err + } + + return os.NewFile(uintptr(fd), ""), nil + }() + + if err != nil { + logger.Error.Println("UAPI listen error:", err) + log.Fatal("Setup failed") + } + // daemonize the process + + device := device.NewDevice(tun, logger) + + errs := make(chan error) + + uapi, err := ipc.UAPIListen(interfaceName, fileUAPI) + if err != nil { + logger.Error.Println("Failed to listen on uapi socket:", err) + log.Fatal("Setup failed") + } + + go func() { + for { + conn, err := uapi.Accept() + if err != nil { + errs <- err + return + } + go device.IpcHandle(conn) + } + }() + + logger.Info.Println("UAPI listener started") + fmt.Println("Device ", device) + + if err := s.CreateNICWithOptions(nic, ep, stack.NICOptions{Name: interfaceName, Disabled: true}); err != nil { + log.Fatal("CreateNIC", err) + } + + return device +} + +func parseAddress(addrName string) (tcpip.Address, tcpip.NetworkProtocolNumber) { + ip := net.ParseIP(addrName) + + if ip.To4() != nil { + return tcpip.Address(ip.To4()), ipv4.ProtocolNumber + } else { + return tcpip.Address(ip.To16()), ipv6.ProtocolNumber + } +} + +func parseSubnet(subnetName string) (tcpip.Address, tcpip.Subnet, tcpip.NetworkProtocolNumber) { + parsedAddr, parsedNet, err := net.ParseCIDR(subnetName) + if err != nil { + log.Fatalf("Bad IP/CIDR address: %v", subnetName) + } + + var addr tcpip.Address + var net tcpip.Address + var proto tcpip.NetworkProtocolNumber + + if parsedAddr.To4() != nil { + addr = tcpip.Address(parsedAddr.To4()) + net = tcpip.Address(parsedNet.IP.To4()) + proto = ipv4.ProtocolNumber + } else { + addr = tcpip.Address(parsedAddr.To16()) + net = tcpip.Address(parsedNet.IP.To16()) + proto = ipv6.ProtocolNumber + } + + // ones, zeros := parsedNet.Mask.Size() + + mask, err := hex.DecodeString(parsedNet.Mask.String()) + if err != nil { + log.Fatalf("Bad mask", err) + } + + subnet, err := tcpip.NewSubnet(net, tcpip.AddressMask(mask)) + if err != nil { + log.Fatalf("Bad subnet", err, net, parsedNet.Mask.String()) + } + + return addr, subnet, proto +} + +func (routes *Routes) addAddress(s *stack.Stack, nic tcpip.NICID, addrName string) tcpip.NetworkProtocolNumber { + // // Parse the IP address. Support both ipv4 and ipv6. + addr, subnet, proto := parseSubnet(addrName) + + if false { + if err := s.AddAddress(nic, proto, addr); err != nil { + log.Fatal("AddAddress", err) + } + } else { + addr := tcpip.ProtocolAddress{ + Protocol: proto, + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: addr, + PrefixLen: subnet.Prefix(), + }} + + fmt.Println("Added address ", addr) + + if err := s.AddProtocolAddress(nic, addr); err != nil { + log.Fatalf("AddProtocolAddress", err, nic) + } + + route := tcpip.Route{ + Destination: addr.AddressWithPrefix.Subnet(), + NIC: nic, + } + + *routes = append(*routes, route) + } + + // subnet, err := tcpip.NewSubnet(tcpip.Address(parsedNet.IP), + // tcpip.AddressMask(parsedNet.Mask)) + // if err != nil { + // log.Fatal(err) + // } + + return proto +} + +type Routes []tcpip.Route + + +func (routes *Routes) OnDuplicateAddressDetectionStatus(nicID tcpip.NICID, addr tcpip.Address, resolved bool, err *tcpip.Error) { + fmt.Println("OnDuplicateAddressDetectionStatus ", addr) +} + +func (routes *Routes) OnDefaultRouterDiscovered(nicID tcpip.NICID, addr tcpip.Address) bool { + fmt.Println("OnDefaultRouterDiscovered") + return true +} + +func (routes *Routes) OnDefaultRouterInvalidated(nicID tcpip.NICID, addr tcpip.Address) { +} + +func (routes *Routes) OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) bool { + fmt.Println("OnOnLinkPrefixDiscovered ", prefix) + return true +} + +func (routes *Routes) OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpip.Subnet) { +} + +func (routes *Routes) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool { + return true +} + +func (routes *Routes) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) { +} + +func (routes *Routes) OnAutoGenAddressInvalidated(tcpip.NICID, tcpip.AddressWithPrefix) { +} + +func (routes *Routes) OnRecursiveDNSServerOption(nicID tcpip.NICID, addrs []tcpip.Address, lifetime time.Duration) { + fmt.Println("OnRecursiveDNSServerOption ", addrs) +} + +func (routes *Routes) OnDHCPv6Configuration(nicID tcpip.NICID, config stack.DHCPv6ConfigurationFromNDPRA) { + str := "undefined" + switch config { + case stack.DHCPv6NoConfiguration: str = "no config" + case stack.DHCPv6ManagedAddress: str = "managed" + case stack.DHCPv6OtherConfigurations: str = "other" + } + + fmt.Println("OnDHCPv6Configuration ", str) +} + + +func (routes *Routes) addRoute(nic tcpip.NICID, routeCfg Route){ + _, dest, _ := parseSubnet(routeCfg.To) + via, _ := parseAddress(routeCfg.Via) + + route := tcpip.Route{ + Destination: dest, + Gateway: via, + NIC: nic, + Mark: routeCfg.Mark, + Markmask: routeCfg.Markmask, + } + + *routes = append(*routes, route) +} + +func protocolToString(proto tcpip.NetworkProtocolNumber) string { + switch proto { + case ipv4.ProtocolNumber: return "ipv4" + case ipv6.ProtocolNumber: return "ipv6" + case arp.ProtocolNumber: return "arp " + default: return "?" + } +} + +func dumpAddress(addr tcpip.ProtocolAddress) { + fmt.Println("Addr: ", protocolToString(addr.Protocol), + addr.AddressWithPrefix) +} + +func dumpAddresses(s *stack.Stack) { + for nic, addrs := range s.AllAddresses() { + fmt.Println("NIC:", nic) + for _, addr := range addrs { + dumpAddress(addr) + } + } +} + +func dumpRoutes(s *stack.Stack) { + for _, r := range s.GetRouteTable() { + fmt.Println("Route:", r) + } +} + +func (routes *Routes) setupCommon(s *stack.Stack, nic tcpip.NICID, id string, cfg Common) { + for _, addr := range cfg.Addresses { + routes.addAddress(s, nic, addr) + } + + for _, route := range cfg.Routes { + fmt.Println("Add Route:", route) + routes.addRoute(nic, route) + } + + for _, route := range *routes { + fmt.Println("Added Route:", route) + } + + // TODO check after enabling the NICs + // if !s.CheckNIC(nic) { + // log.Fatal("not usable ", id) + // } +} + +func (routes *Routes) setupLoopback(s *stack.Stack, nic tcpip.NICID, id string, eth *Ethernet) { + fmt.Println("Ethernet", id, nic, eth) + + linkEP := loopback.New() + if err := s.CreateNICWithOptions(nic, linkEP, stack.NICOptions{Name: id, Disabled: true}); err != nil { + log.Fatal("CreateNIC", err) + } + + routes.setupCommon(s, nic, id, eth.Common) +} + +func (routes *Routes) setupTunnel(s *stack.Stack, nic tcpip.NICID, id string, tun *Tunnel) { + fmt.Println("TUN", id, nic, tun) + + maddr, err := net.ParseMAC(tun.Macaddress) + if err != nil { + log.Fatalf("Bad MAC address: %v", tun.Macaddress) + } + + addRouterLink(s, nic, id, tcpip.LinkAddress(maddr), tun) + fmt.Println("Tunnel 20", tun.Conn) + routes.setupCommon(s, nic, id, tun.Common) + fmt.Println("Tunnel 21", tun.Conn) +} + +func (routes *Routes) setupTuntap(s *stack.Stack, nic tcpip.NICID, id string, tun *Tuntap) { + fmt.Println("Tuntap", id, nic, tun) + + maddr, err := net.ParseMAC(tun.Macaddress) + if err != nil { + log.Fatalf("Bad MAC address: %v", tun.Macaddress) + } + + var tap bool + switch tun.Mode { + case "tun": + tap = false + case "tap": + tap = true + default: + log.Fatalf("Bad mode: %v", tun.Mode) + } + + addTunLink(s, nic, id, tap, tcpip.LinkAddress(maddr), tun) + routes.setupCommon(s, nic, id, tun.Common) +} + +func (routes *Routes) setupWG(s *stack.Stack, nic tcpip.NICID, id string, wg *Wireguard) { + fmt.Println("WG", id, nic, wg.ListenPort, wg) + fmt.Printf("Peers %v\n", wg.Peers) + + maddr, err := net.ParseMAC(wg.Macaddress) + if err != nil { + log.Fatalf("Bad MAC address: %v", wg.Macaddress) + } + + //addTunLink(s, tunNic, tunName, tcpip.LinkAddress(tapMaddr)) + device := addWgLink(s, nic, wg.Name, tcpip.LinkAddress(maddr)) + + var wgCmd strings.Builder + fmt.Fprintf(&wgCmd, "private_key=%s\nlisten_port=%d\nreplace_peers=true\n", + hex.EncodeToString(wg.PrivateKey), wg.ListenPort) + for _, peer := range wg.Peers { + fmt.Fprintf(&wgCmd, "public_key=%s\nendpoint=%s\npersistent_keepalive_interval=%d\nreplace_allowed_ips=true\n", + hex.EncodeToString(peer.PublicKey), peer.Endpoint, peer.PersistentKeepalive) + for _, allowedIp := range peer.AllowedIPs { + fmt.Fprintf(&wgCmd, "allowed_ip=%s\n", allowedIp) + } + wgCmd.WriteString("\n") + } + str := wgCmd.String() + fmt.Println("IpcSetOperation", str) + device.IpcSetOperation(bufio.NewReader(strings.NewReader(str))) + + routes.setupCommon(s, nic, id, wg.Common) + + go func() { + fmt.Println("Starting ", nic) + + select { + // case <-term: + // case <-errs: + case <-device.Wait(): + } + + fmt.Println("Finnished ", nic) + }() +} + +func KeepAliveTunnel(np *Netplan) { + KeepAliveTunnelEx(np, true) +} + +func KeepAliveTunnelEx(np *Netplan, debug bool) { + for _, tun := range np.Network.Tunnels { + if debug { + fmt.Println("Tunnel ", tun.Mode, tun.Local, tun.Remote, tun.Conn, tun.Sd) + } + runtime.KeepAlive(tun.Conn) + } +} + +func withIAPD(iaid [4]byte, prefixLength int) dhcpv6.Modifier { + return func(d dhcpv6.DHCPv6) { + opt := d.GetOneOption(dhcpv6.OptionIAPD) + if opt == nil { + opt = &dhcpv6.OptIAPD{} + } + iaPd := opt.(*dhcpv6.OptIAPD) + + if prefixLength > 0 { + iaPrefix := &dhcpv6.OptIAPrefix{} + iaPrefix.Prefix.Mask = net.CIDRMask(prefixLength, + 128-prefixLength) + iaPrefix.Prefix.IP = net.ParseIP("::") + iaPd.Options.Add(iaPrefix) + } + copy(iaPd.IaId[:], iaid[:]) + d.UpdateOption(iaPd) + } +} + +func withIAPDFromAdvertise(adv *dhcpv6.Message) dhcpv6.Modifier { + return func(d dhcpv6.DHCPv6) { + opt := adv.GetOneOption(dhcpv6.OptionIAPD) + if opt != nil { + d.AddOption(opt) + } + } +} + +func withDHCP4oDHCP6Server(addrs ...net.IP) dhcpv6.Modifier { + return func(d dhcpv6.DHCPv6) { + opt := dhcpv6.OptDHCP4oDHCP6Server{ + DHCP4oDHCP6Servers: addrs, + } + d.UpdateOption(&opt) + } +} + +func withDHCPv4Msg(msg *dhcpv4.DHCPv4) dhcpv6.Modifier { + return func(d dhcpv6.DHCPv6) { + opt := dhcpv6.OptDHCPv4Msg{ + Msg: msg, + } + d.UpdateOption(&opt) + } +} + +func NewDHCPv4Query(modifiers ...dhcpv6.Modifier) (*dhcpv6.Message, error) { + msg, err := dhcpv6.NewMessage() + if err != nil { + return nil, err + } + //msg.MessageType = dhcpv6.MessageTypeDHCPv4Query FIXME + msg.MessageType = 20 + msg.AddOption(&dhcpv6.OptElapsedTime{}) + //modifier = append([]dhcpv6.Modifier{WithRequestedOptions()}, modifier...} + for _, mod := range modifiers { + mod(msg) + } + return msg, nil +} + +func (routes *Routes) doClient(s *stack.Stack, nic tcpip.NICID) { + fmt.Println("doClient start") + + // TODO use link local address + + src := tcpip.Address(net.ParseIP("2001:470:dfae:6300::1:111").To16()) + dst := tcpip.Address(net.ParseIP("2001:470:dfae:6300::3").To16()) + + conn, err := gonet.DialUDP(s, + &tcpip.FullAddress{NIC: nic, Addr: src, Port: 546}, + &tcpip.FullAddress{NIC: nic, Addr: dst, Port: 547}, + ipv6.ProtocolNumber) + if err != nil { + log.Fatal(err) + } + + hwaddr := []byte("ABCDEF") + + client, err := nclient6.NewWithConn(conn, hwaddr, nclient6.WithDebugLogger()) + if err != nil { + log.Fatal(err) + } + + duid := dhcpv6.Duid{ + Type: dhcpv6.DUID_LL, + HwType: iana.HWTypeEthernet, + LinkLayerAddr: hwaddr, + } + + fqdnOpt := dhcpv6.WithFQDN(0x1, "gvisor.m7n.se") + + iaPrefix := dhcpv6.OptIAPrefix{} + iaPrefix.Prefix.Mask = net.CIDRMask(64, 128-64) + //iaPrefix.SetIPv6Prefix(net.ParseIP("::")) + + iaid := []byte{0, 0, 0, 3} + ident := []byte{255} // Type IAID+DUID + ident = append(ident, iaid...) // IAID + ident = append(ident, duid.ToBytes()...) // DUID + clientIDOpt := dhcpv4.OptClientIdentifier(ident) + + adv, err := client.Solicit(context.Background(), + dhcpv6.WithIAPD([4]byte{0, 0, 0, 1}, &iaPrefix), + dhcpv6.WithIAID([4]byte{0, 0, 0, 2}), + fqdnOpt, + dhcpv6.WithClientID(duid), + dhcpv6.WithRequestedOptions(dhcpv6.OptionDHCP4oDHCP6Server), + withDHCP4oDHCP6Server(net.ParseIP("fe80::1"), + net.ParseIP("fe80::2"))) + if err != nil { + log.Fatal(err) + } + + //withIAPDFromAdvertise(adv) + msg, err := client.Request(context.Background(), adv, fqdnOpt) + if err != nil { + log.Fatal(err) + } + + disc, err := dhcpv4.NewDiscovery(hwaddr, dhcpv4.WithOption(clientIDOpt)) + if err != nil { + log.Fatal(err) + } + disc_query, err := NewDHCPv4Query(withDHCPv4Msg(disc)) + if err != nil { + log.Fatal(err) + } + + serverAddr := nclient6.AllDHCPRelayAgentsAndServers + disc_resp, err := client.SendAndRead(context.Background(), serverAddr, disc_query, nil) + if err != nil { + log.Fatal(err) + } + + offer := disc_resp.GetOneOption(dhcpv6.OptionDHCPv4Msg).(*dhcpv6.OptDHCPv4Msg).Msg + req, err := dhcpv4.NewRequestFromOffer(offer) + if err != nil { + log.Fatal(err) + } + + req_query, err := NewDHCPv4Query(withDHCPv4Msg(req)) + if err != nil { + log.Fatal(err) + } + ack, err := client.SendAndRead(context.Background(), serverAddr, req_query, nil) + if err != nil { + log.Fatal(err) + } + + // client.Close() + fmt.Println("doClient end", ack) + + iana := msg.GetOneOption(dhcpv6.OptionIANA).(*dhcpv6.OptIANA) + for _, addr := range iana.Options.Get(dhcpv6.OptionIAAddr) { + str := addr.(*dhcpv6.OptIAAddress).IPv6Addr.String() + "/128" + routes.addAddress(s, nic, str) + + _, dest, _ := parseSubnet(addr.(*dhcpv6.OptIAAddress).IPv6Addr.String() + "/64") + route := tcpip.Route{ + Destination: dest, + NIC: nic, + } + + *routes = append(*routes, route) + } + + var loNic tcpip.NICID = 0 + iapd := msg.GetOneOption(dhcpv6.OptionIAPD).(*dhcpv6.OptIAPD) + for _, opt := range iapd.Options.Get(dhcpv6.OptionIAPrefix) { + prefix := opt.(*dhcpv6.OptIAPrefix) + str := prefix.Prefix.IP.String()+"1"+"/128" + routes.addAddress(s, loNic, str) + } + + dumpAddresses(s) + dumpRoutes(s) + + if false { + // FIXME can't send non icmpv6 echo request + var wq waiter.Queue + ep, e := s.NewEndpoint(icmp.ProtocolNumber6, ipv6.ProtocolNumber, &wq) + if err != nil { + log.Fatal("NewEndpoint", e) + } + + v := []byte{0, 1, 2, 3} + raSrc := tcpip.FullAddress{NIC: nic, Addr: src, Port: 0} + raDst := tcpip.FullAddress{NIC: nic, Addr: dst, Port: 0} + if err := ep.Bind(raSrc); err != nil { + log.Fatal("Bind failed: ", err) + } + fmt.Println("Before write", raSrc.NIC, raSrc.Addr, raSrc.Port, raDst.NIC, raDst.Addr, raDst.Port) + ep.Write(tcpip.SlicePayload(v), + tcpip.WriteOptions{To:&raDst}) + + defer ep.Close() + fmt.Println("After write") + } + // Exchange runs a Solicit-Advertise-Request-Reply transaction on the + // specified network interface, and returns a list of DHCPv6 packets + // (a "conversation") and an error if any. Notice that Exchange may + // return a non-empty packet list even if there is an error. This is + // intended, because the transaction may fail at any point, and we + // still want to know what packets were exchanged until then. + // A default Solicit packet will be used during the "conversation", + // which can be manipulated by using modifiers. +// conversation, err := client.Exchange(iface) + + // Summary() prints a verbose representation of the exchanged packets. +// for _, packet := range conversation { +// log.Print(packet.Summary()) +// } + // error handling is done *after* printing, so we still print the + // exchanged packets if any, as explained above. +// if err != nil { +// log.Fatal(err) +// } +} + +func main() { + flag.Parse() + if len(flag.Args()) != 1 { + log.Fatal("Usage: ", os.Args[0], " <local-port>") + } + + data, err := ioutil.ReadFile("config.yaml") + if err != nil { + log.Fatalf("File reading error", err) + } + + var np Netplan + err = yaml.Unmarshal(data, &np) + fmt.Println("err", err) + fmt.Println("res", np) + + portName := flag.Arg(0) + + rand.Seed(time.Now().UnixNano()) + + localPort, err := strconv.Atoi(portName) + if err != nil { + log.Fatalf("Unable to convert port %v: %v", portName, err) + } + + routes := Routes{} + + // Create the stack with ip and tcp protocols, then add a tun-based + // NIC and address. + s := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol(), ipv6.NewProtocol(), arp.NewProtocol()}, + TransportProtocols: []stack.TransportProtocol{ + tcp.NewProtocol(), + udp.NewProtocol(), + //icmp.NewProtocol6(), + }, + //NDPConfigs: stack.DefaultNDPConfigurations(), + //NDPDisp: &routes, + }) + + // FIXME enable + s.SetForwarding(true) + + var nic tcpip.NICID = -1 + var wg2Nic tcpip.NICID = -1 + + for id, tun := range np.Network.Ethernets { + nic = nic + 1 + routes.setupLoopback(s, nic, id, tun) + } + + for id, tun := range np.Network.Tuntaps { + nic = nic + 1 + routes.setupTuntap(s, nic, id, tun) + } + + for id, wg := range np.Network.Wireguards { + nic = nic + 1 + if id == "wg2" { + wg2Nic = nic + } + routes.setupWG(s, nic, id, wg) + } + + for id, tun := range np.Network.Tunnels { + nic = nic + 1 + routes.setupTunnel(s, nic, id, tun) + } + + nicCount := nic + + for nic = 0; nic < nicCount; nic++ { + s.EnableNIC(nic) + } + + KeepAliveTunnel(&np) + + // Sort route table for longest prefix match + sort.Slice(routes, func(i, j int) bool { + return routes[i].Destination.Prefix() > routes[j].Destination.Prefix() + }) + + s.SetRouteTable(routes) + + // FIXME disabled for now, to test startSolicitingRouters + if true { + routes.doClient(s, wg2Nic) + } + + // Sort route table for longest prefix match + sort.Slice(routes, func(i, j int) bool { + return routes[i].Destination.Prefix() > routes[j].Destination.Prefix() + }) + + s.SetRouteTable(routes) + + dumpAddresses(s) + dumpRoutes(s) + + KeepAliveTunnel(&np) + + runtime.GC() + + KeepAliveTunnel(&np) + + // Create TCP endpoint, bind it, then start listening. + if true { + proto := ipv6.ProtocolNumber // Dummy + var wq waiter.Queue + ep, e := s.NewEndpoint(tcp.ProtocolNumber, proto, &wq) + if err != nil { + log.Fatal("NewEndpoint", e) + } + + defer ep.Close() + + if err := ep.Bind(tcpip.FullAddress{0, "", uint16(localPort)}); err != nil { + log.Fatal("Bind failed: ", err) + +} + if err := ep.Listen(10); err != nil { + log.Fatal("Listen failed: ", err) + } + + // Wait for connections to appear. + waitEntry, notifyCh := waiter.NewChannelEntry(nil) + wq.EventRegister(&waitEntry, waiter.EventIn) + defer wq.EventUnregister(&waitEntry) + + fmt.Println("Echo server\n") + + for { + n, wq, err := ep.Accept() + if err != nil { + if err == tcpip.ErrWouldBlock { + <-notifyCh + continue + } + + log.Fatal("Accept() failed:", err) + } + + KeepAliveTunnelEx(&np, false) + go echo(wq, n) + } + } + +} diff --git a/pkg/tcpip/sample/wg_tunnel/run.sh b/pkg/tcpip/sample/wg_tunnel/run.sh new file mode 100755 index 000000000..7b5bf0524 --- /dev/null +++ b/pkg/tcpip/sample/wg_tunnel/run.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +usage() { + echo "$0 [-6] [-tap] -dev <dev>" + exit +} + +IPV6=0 +OPTS= +DEV= + +while [ $# -gt 0 ]; do + case $1 in + -4) IPV6=0;; + -6) IPV6=1;; + -tap) OPTS="$OPTS -tap";; + -dev) DEV="$2"; shift;; + *) usage;; + esac + + shift +done + +if [ -z "$DEV" ]; then + usage +fi + +if [ $IPV6 -eq 1 ]; then + OPTS="$OPTS $DEV 2001:470:de6f:5311::2/64 10003 2001:470:de6f:5312::1/64 2001:470:de6f:5311::1" +else + OPTS="$OPTS $DEV 10.1.1.2/24 10003 10.1.2.1/24 10.1.1.1" +fi + +exec ./wg_tunnel $OPTS diff --git a/pkg/tcpip/sample/wg_tunnel/setup.sh b/pkg/tcpip/sample/wg_tunnel/setup.sh new file mode 100755 index 000000000..043a40be8 --- /dev/null +++ b/pkg/tcpip/sample/wg_tunnel/setup.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +setup() { + local mode=$1 + local dev=$2 + ip tuntap add $dev mode $mode + ip l set up $dev + ip a a 10.1.1.1/24 dev $dev + ip r a 10.1.0.0/16 via 10.1.1.2 proto static + ip a a 2001:470:de6f:5311::1/64 dev $dev + ip r a 2001:470:de6f:5312::/64 via 2001:470:de6f:5311::2 proto static + ip r a unreachable 2001:470:de6f:5310::/56 proto static || true +} + +usage() { + echo "Usage: $0 <mode> <dev>" + exit 1 +} + +if [ $# -ne 2 ]; then + usage +fi + +setup $1 $2 |