package core import ( "context" "fmt" "net" "time" "github.com/google/gopacket/layers" "github.com/pkg/errors" "github.com/wencaiwulue/kubevpn/v2/pkg/config" plog "github.com/wencaiwulue/kubevpn/v2/pkg/log" "github.com/wencaiwulue/kubevpn/v2/pkg/util" ) func (h *tunHandler) HandleClient(ctx context.Context, tun net.Conn, forwarder *Forwarder) { device := &ClientDevice{ tun: tun, tunInbound: make(chan *Packet, MaxSize), tunOutbound: make(chan *Packet, MaxSize), errChan: h.errChan, } defer device.Close() go device.handlePacket(ctx, forwarder) go device.readFromTun(ctx) go device.writeToTun(ctx) go device.heartbeats(ctx) select { case <-device.errChan: case <-ctx.Done(): } } type ClientDevice struct { tun net.Conn tunInbound chan *Packet tunOutbound chan *Packet errChan chan error } func (d *ClientDevice) handlePacket(ctx context.Context, forward *Forwarder) { for ctx.Err() == nil { func() { subCtx, cancelFunc := context.WithCancel(ctx) defer cancelFunc() defer time.Sleep(time.Second * 2) conn, err := forwardConn(subCtx, forward) if err != nil { plog.G(ctx).Errorf("Failed to get remote conn from %s -> %s: %s", d.tun.LocalAddr(), forward.node.Remote, err) return } defer conn.Close() errChan := make(chan error, 2) go readFromConn(subCtx, conn, d.tunInbound, d.tunOutbound, errChan) go writeToConn(subCtx, conn, d.tunInbound, errChan) select { case err = <-errChan: plog.G(ctx).Errorf("Failed to transport data to remote %s: %v", conn.RemoteAddr(), err) case <-ctx.Done(): return } }() } } func forwardConn(ctx context.Context, forwarder *Forwarder) (net.Conn, error) { conn, err := forwarder.DialContext(ctx) if err != nil { return nil, errors.Wrap(err, "failed to dial forwarder") } return conn, nil } func readFromConn(ctx context.Context, conn net.Conn, tunInbound chan *Packet, tunOutbound chan *Packet, errChan chan error) { defer util.HandleCrash() var gvisorInbound = make(chan *Packet, MaxSize) go handleGvisorPacket(gvisorInbound, tunInbound).Run(ctx) for ctx.Err() == nil { buf := config.LPool.Get().([]byte)[:] err := conn.SetReadDeadline(time.Now().Add(config.KeepAliveTime)) if err != nil { config.LPool.Put(buf[:]) plog.G(ctx).Errorf("Failed to set read deadline: %v", err) util.SafeWrite(errChan, errors.Wrap(err, "failed to set read deadline")) return } n, err := conn.Read(buf[:]) if err != nil { config.LPool.Put(buf[:]) plog.G(ctx).Errorf("Failed to read packet from remote: %v", err) util.SafeWrite(errChan, errors.Wrap(err, fmt.Sprintf("failed to read packet from remote %s", conn.RemoteAddr()))) return } if n < 1 { plog.G(ctx).Warnf("Packet length 0") config.LPool.Put(buf[:]) continue } if buf[0] == 1 { gvisorInbound <- NewPacket(buf[:], n, nil, nil) } else { tunOutbound <- NewPacket(buf[:], n, nil, nil) } } } func writeToConn(ctx context.Context, conn net.Conn, inbound <-chan *Packet, errChan chan error) { defer util.HandleCrash() for ctx.Err() == nil { var packet *Packet select { case packet = <-inbound: if packet == nil { return } case <-ctx.Done(): return } err := conn.SetWriteDeadline(time.Now().Add(config.KeepAliveTime)) if err != nil { plog.G(ctx).Errorf("Failed to set write deadline: %v", err) util.SafeWrite(errChan, errors.Wrap(err, "failed to set write deadline")) return } _, err = conn.Write(packet.data[:packet.length]) config.LPool.Put(packet.data[:]) if err != nil { plog.G(ctx).Errorf("Failed to write packet to remote: %v", err) util.SafeWrite(errChan, errors.Wrap(err, "failed to write packet to remote")) return } } } func (d *ClientDevice) readFromTun(ctx context.Context) { defer util.HandleCrash() var gvisorInbound = make(chan *Packet, MaxSize) go handleGvisorPacket(gvisorInbound, d.tunOutbound).Run(ctx) for ctx.Err() == nil { buf := config.LPool.Get().([]byte)[:] n, err := d.tun.Read(buf[1:]) if err != nil { plog.G(ctx).Errorf("Failed to read packet from tun device: %s", err) util.SafeWrite(d.errChan, err) config.LPool.Put(buf[:]) return } // local client handle it with gvisor buf[0] = 1 // Try to determine network protocol number, default zero. var src, dst net.IP var protocol int src, dst, protocol, err = util.ParseIP(buf[1 : n+1]) if err != nil { plog.G(ctx).Errorf("Unknown packet: %v", err) config.LPool.Put(buf[:]) continue } plog.G(context.Background()).Debugf("SRC: %s, DST: %s, Protocol: %s, Length: %d", src, dst, layers.IPProtocol(protocol).String(), n) packet := NewPacket(buf[:], n+1, src, dst) if packet.src.Equal(packet.dst) { gvisorInbound <- packet } else { d.tunInbound <- packet } } } func (d *ClientDevice) writeToTun(ctx context.Context) { defer util.HandleCrash() for ctx.Err() == nil { var packet *Packet select { case packet = <-d.tunOutbound: if packet == nil { return } case <-ctx.Done(): return } _, err := d.tun.Write(packet.data[1:packet.length]) config.LPool.Put(packet.data[:]) if err != nil { plog.G(ctx).Errorf("Failed to write packet to tun device: %v", err) util.SafeWrite(d.errChan, err) return } } } func (d *ClientDevice) Close() { d.tun.Close() } func (d *ClientDevice) heartbeats(ctx context.Context) { tunIfi, err := util.GetTunDeviceByConn(d.tun) if err != nil { plog.G(ctx).Errorf("Failed to get tun device: %v", err) return } srcIPv4, srcIPv6, dockerSrcIPv4, err := util.GetTunDeviceIP(tunIfi.Name) if err != nil { plog.G(ctx).Errorf("Failed to get tun device %s IP: %v", tunIfi.Name, err) return } ticker := time.NewTicker(config.KeepAliveTime) defer ticker.Stop() var bytes, bytes6 []byte for ; ctx.Err() == nil; <-ticker.C { if srcIPv4 != nil { bytes, err = util.GenICMPPacket(srcIPv4, config.RouterIP) if err != nil { plog.G(ctx).Errorf("Failed to generate IPv4 packet: %v", err) } else { data := config.LPool.Get().([]byte)[:] length := copy(data[1:], bytes) data[0] = 1 d.tunInbound <- &Packet{ data: data[:], length: length + 1, } } } if srcIPv6 != nil { bytes6, err = util.GenICMPPacketIPv6(srcIPv6, config.RouterIP6) if err != nil { plog.G(ctx).Errorf("Failed to generate IPv6 packet: %v", err) } else { data := config.LPool.Get().([]byte)[:] length := copy(data[1:], bytes6) data[0] = 1 d.tunInbound <- &Packet{ data: data[:], length: length + 1, } } } if dockerSrcIPv4 != nil { _, _ = util.Ping(ctx, dockerSrcIPv4.String(), config.DockerRouterIP.String()) } } }