|
| 1 | +/* |
| 2 | + ********************************************************************** |
| 3 | + * ------------------------------------------------------------------- |
| 4 | + * Project Name : Abdal 4iProto Client |
| 5 | + * File Name : ip_defrag.go |
| 6 | + * Author : Ebrahim Shafiei (EbraSha) |
| 7 | + * Email : Prof.Shafiei@Gmail.com |
| 8 | + * Created On : 2025-12-14 15:17:47 |
| 9 | + * Description : IP packet defragmentation module using gopacket for handling fragmented UDP packets in SOCKS5 proxy |
| 10 | + * ------------------------------------------------------------------- |
| 11 | + * |
| 12 | + * "Coding is an engaging and beloved hobby for me. I passionately and insatiably pursue knowledge in cybersecurity and programming." |
| 13 | + * – Ebrahim Shafiei |
| 14 | + * |
| 15 | + ********************************************************************** |
| 16 | + */ |
| 17 | + |
| 18 | +package main |
| 19 | + |
| 20 | +import ( |
| 21 | + "fmt" |
| 22 | + "net" |
| 23 | + "os" |
| 24 | + "runtime" |
| 25 | + "sync" |
| 26 | + "syscall" |
| 27 | + |
| 28 | + "github.com/google/gopacket" |
| 29 | + "github.com/google/gopacket/ip4defrag" |
| 30 | + "github.com/google/gopacket/layers" |
| 31 | + "golang.org/x/net/ipv4" |
| 32 | +) |
| 33 | + |
| 34 | +// IP_HDRINCL constant for Windows (value is 2) |
| 35 | +const IP_HDRINCL = 2 |
| 36 | + |
| 37 | +// IPDefragmenter handles IP packet defragmentation for UDP packets |
| 38 | +type IPDefragmenter struct { |
| 39 | + defragger *ip4defrag.IPv4Defragmenter |
| 40 | + mu sync.Mutex |
| 41 | +} |
| 42 | + |
| 43 | +// NewIPDefragmenter creates a new IP defragmenter instance |
| 44 | +func NewIPDefragmenter() *IPDefragmenter { |
| 45 | + return &IPDefragmenter{ |
| 46 | + defragger: ip4defrag.NewIPv4Defragmenter(), |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +// DefragmentIPPacket processes an IP packet and returns the defragmented packet if complete |
| 51 | +func (d *IPDefragmenter) DefragmentIPPacket(ipPacket []byte) ([]byte, error) { |
| 52 | + d.mu.Lock() |
| 53 | + defer d.mu.Unlock() |
| 54 | + |
| 55 | + // Parse the IP packet |
| 56 | + packet := gopacket.NewPacket(ipPacket, layers.LayerTypeIPv4, gopacket.Default) |
| 57 | + ipLayer := packet.Layer(layers.LayerTypeIPv4) |
| 58 | + if ipLayer == nil { |
| 59 | + return nil, fmt.Errorf("not an IPv4 packet") |
| 60 | + } |
| 61 | + |
| 62 | + ip, _ := ipLayer.(*layers.IPv4) |
| 63 | + |
| 64 | + // Check if packet is fragmented |
| 65 | + isFragmented := (ip.Flags&layers.IPv4MoreFragments != 0) || (ip.FragOffset > 0) |
| 66 | + if !isFragmented { |
| 67 | + // Not fragmented, return as is |
| 68 | + return ipPacket, nil |
| 69 | + } |
| 70 | + |
| 71 | + // Defragment the packet |
| 72 | + defragged, err := d.defragger.DefragIPv4(ip) |
| 73 | + if err != nil { |
| 74 | + return nil, fmt.Errorf("defragmentation error: %w", err) |
| 75 | + } |
| 76 | + |
| 77 | + if defragged == nil { |
| 78 | + // Packet is still being assembled, return nil to indicate incomplete |
| 79 | + return nil, nil |
| 80 | + } |
| 81 | + |
| 82 | + // Serialize the defragmented packet |
| 83 | + buf := gopacket.NewSerializeBuffer() |
| 84 | + opts := gopacket.SerializeOptions{ |
| 85 | + FixLengths: true, |
| 86 | + ComputeChecksums: true, |
| 87 | + } |
| 88 | + err = defragged.SerializeTo(buf, opts) |
| 89 | + if err != nil { |
| 90 | + return nil, fmt.Errorf("failed to serialize defragmented packet: %w", err) |
| 91 | + } |
| 92 | + |
| 93 | + return buf.Bytes(), nil |
| 94 | +} |
| 95 | + |
| 96 | +// UDPPacketReader reads UDP packets from a raw socket with IP defragmentation support |
| 97 | +type UDPPacketReader struct { |
| 98 | + defragger *IPDefragmenter |
| 99 | + rawConn *ipv4.PacketConn |
| 100 | + udpConn *net.UDPConn |
| 101 | + useRaw bool |
| 102 | + localUDPPort int |
| 103 | +} |
| 104 | + |
| 105 | +// NewUDPPacketReader creates a new UDP packet reader with defragmentation support |
| 106 | +// Attempts to use raw socket for IP-level packet capture, falls back to regular UDP if unavailable |
| 107 | +// Note: Raw sockets require administrator privileges on Windows |
| 108 | +func NewUDPPacketReader(udpConn *net.UDPConn) (*UDPPacketReader, error) { |
| 109 | + reader := &UDPPacketReader{ |
| 110 | + defragger: NewIPDefragmenter(), |
| 111 | + udpConn: udpConn, |
| 112 | + useRaw: false, |
| 113 | + localUDPPort: udpConn.LocalAddr().(*net.UDPAddr).Port, |
| 114 | + } |
| 115 | + |
| 116 | + // Try to create a raw socket for IP-level packet capture |
| 117 | + // This requires administrator privileges on Windows |
| 118 | + rawSocket, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_IP) |
| 119 | + if err != nil { |
| 120 | + // Raw socket not available (likely no admin privileges), fall back to regular UDP |
| 121 | + // OS will handle defragmentation, but we can't intercept fragmented packets |
| 122 | + return reader, nil |
| 123 | + } |
| 124 | + |
| 125 | + // Set socket options to receive IP headers |
| 126 | + // Use platform-specific constant for IP_HDRINCL |
| 127 | + var ipHdrIncl int |
| 128 | + if runtime.GOOS == "windows" { |
| 129 | + ipHdrIncl = IP_HDRINCL |
| 130 | + } else { |
| 131 | + // On Unix systems, try to use syscall.IP_HDRINCL if available |
| 132 | + // If not available, we'll skip this option |
| 133 | + ipHdrIncl = 1 // Try a generic value, may not work on all systems |
| 134 | + } |
| 135 | + |
| 136 | + err = syscall.SetsockoptInt(rawSocket, syscall.IPPROTO_IP, ipHdrIncl, 1) |
| 137 | + if err != nil { |
| 138 | + syscall.Close(rawSocket) |
| 139 | + return reader, nil |
| 140 | + } |
| 141 | + |
| 142 | + // Create a file from the socket descriptor |
| 143 | + file := os.NewFile(uintptr(rawSocket), "raw-socket") |
| 144 | + if file == nil { |
| 145 | + syscall.Close(rawSocket) |
| 146 | + return reader, nil |
| 147 | + } |
| 148 | + |
| 149 | + // Create a packet connection from the file (not regular connection) |
| 150 | + packetConn, err := net.FilePacketConn(file) |
| 151 | + file.Close() // Close the file, we have the connection now |
| 152 | + if err != nil { |
| 153 | + syscall.Close(rawSocket) |
| 154 | + return reader, nil |
| 155 | + } |
| 156 | + |
| 157 | + // Create ipv4.PacketConn from the packet connection |
| 158 | + rawConn := ipv4.NewPacketConn(packetConn) |
| 159 | + if rawConn == nil { |
| 160 | + packetConn.Close() |
| 161 | + return reader, nil |
| 162 | + } |
| 163 | + |
| 164 | + reader.rawConn = rawConn |
| 165 | + reader.useRaw = true |
| 166 | + return reader, nil |
| 167 | +} |
| 168 | + |
| 169 | +// ReadUDPPacket reads a complete UDP packet, handling fragmentation if needed |
| 170 | +func (r *UDPPacketReader) ReadUDPPacket() ([]byte, *net.UDPAddr, error) { |
| 171 | + if r.useRaw && r.rawConn != nil { |
| 172 | + // Use raw socket with defragmentation |
| 173 | + buf := make([]byte, 65535) |
| 174 | + for { |
| 175 | + n, cm, src, err := r.rawConn.ReadFrom(buf) |
| 176 | + if err != nil { |
| 177 | + return nil, nil, err |
| 178 | + } |
| 179 | + |
| 180 | + // Extract source IP from control message or address |
| 181 | + var srcIP net.IP |
| 182 | + if cm != nil && cm.Src != nil { |
| 183 | + // cm.Src is net.IP (which is []byte) |
| 184 | + srcIP = cm.Src |
| 185 | + } else if src != nil { |
| 186 | + if ipAddr, ok := src.(*net.IPAddr); ok { |
| 187 | + srcIP = ipAddr.IP |
| 188 | + } else if udpAddr, ok := src.(*net.UDPAddr); ok { |
| 189 | + srcIP = udpAddr.IP |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + // Parse IP packet |
| 194 | + packet := gopacket.NewPacket(buf[:n], layers.LayerTypeIPv4, gopacket.Default) |
| 195 | + ipLayer := packet.Layer(layers.LayerTypeIPv4) |
| 196 | + if ipLayer == nil { |
| 197 | + continue |
| 198 | + } |
| 199 | + |
| 200 | + ip, _ := ipLayer.(*layers.IPv4) |
| 201 | + |
| 202 | + // Use source IP from packet if not available from control message |
| 203 | + if srcIP == nil { |
| 204 | + srcIP = ip.SrcIP |
| 205 | + } |
| 206 | + |
| 207 | + // Check if packet is fragmented |
| 208 | + isFragmented := (ip.Flags&layers.IPv4MoreFragments != 0) || (ip.FragOffset > 0) |
| 209 | + if isFragmented { |
| 210 | + // Defragment the packet |
| 211 | + defragged, err := r.defragger.DefragmentIPPacket(buf[:n]) |
| 212 | + if err != nil { |
| 213 | + // Error during defragmentation, skip this packet |
| 214 | + continue |
| 215 | + } |
| 216 | + if defragged == nil { |
| 217 | + // Still assembling, skip this packet and wait for more fragments |
| 218 | + continue |
| 219 | + } |
| 220 | + |
| 221 | + // Re-parse the defragmented packet |
| 222 | + packet = gopacket.NewPacket(defragged, layers.LayerTypeIPv4, gopacket.Default) |
| 223 | + ipLayer = packet.Layer(layers.LayerTypeIPv4) |
| 224 | + if ipLayer == nil { |
| 225 | + continue |
| 226 | + } |
| 227 | + ip, _ = ipLayer.(*layers.IPv4) |
| 228 | + } |
| 229 | + |
| 230 | + // Extract UDP layer |
| 231 | + udpLayer := packet.Layer(layers.LayerTypeUDP) |
| 232 | + if udpLayer == nil { |
| 233 | + continue |
| 234 | + } |
| 235 | + |
| 236 | + udp, _ := udpLayer.(*layers.UDP) |
| 237 | + |
| 238 | + // Check if this UDP packet is for our port |
| 239 | + if int(udp.DstPort) != r.localUDPPort { |
| 240 | + continue |
| 241 | + } |
| 242 | + |
| 243 | + // Extract source address |
| 244 | + clientAddr := &net.UDPAddr{ |
| 245 | + IP: srcIP, |
| 246 | + Port: int(udp.SrcPort), |
| 247 | + } |
| 248 | + |
| 249 | + // Return the UDP payload (SOCKS5 packet) |
| 250 | + return udp.Payload, clientAddr, nil |
| 251 | + } |
| 252 | + } |
| 253 | + |
| 254 | + // Fall back to regular UDP socket reading |
| 255 | + // The OS should handle defragmentation, but we'll use larger buffer |
| 256 | + buf := make([]byte, 65535) |
| 257 | + n, clientAddr, err := r.udpConn.ReadFromUDP(buf) |
| 258 | + if err != nil { |
| 259 | + return nil, nil, err |
| 260 | + } |
| 261 | + return buf[:n], clientAddr, nil |
| 262 | +} |
| 263 | + |
| 264 | +// Close closes the packet reader |
| 265 | +func (r *UDPPacketReader) Close() error { |
| 266 | + if r.rawConn != nil { |
| 267 | + r.rawConn.Close() |
| 268 | + } |
| 269 | + return nil |
| 270 | +} |
0 commit comments