@@ -10,9 +10,12 @@ import (
"math/rand"
"net"
"os"
"regexp"
"strings"
"syscall"
"time"
"golang.org/x/sys/unix"
"gopkg.in/yaml.v3"
)
@@ -278,16 +281,20 @@ func (pk *PortKnocker) knockTarget(target Target, verbose bool) error {
}
// Вычисляем таймаут как половину интервала между пакетами
timeout := time . Duration ( target . Delay ) / 2
if timeout < 100 * time . Millisecond {
timeout = 100 * time . Millisecond // минимальный таймаут
}
timeout := max ( time . Duration ( target . Delay ) / 2 ,
// минимальный таймаут
100 * time . Millisecond )
for i , port := range target . Ports {
if verbose {
if target . Gateway != "" {
fmt . Printf ( " Отправка пакета на %s:%d (%s) через шлюз %s\n" , target . Host , port , protocol , target . Gateway )
} else {
fmt . Printf ( " Отправка пакета на %s:%d (%s)\n" , target . Host , port , protocol )
}
}
if err := pk . sendPacket ( target . Host , port , protocol , target . WaitConnection , timeout , target . Gateway ) ; err != nil {
if target . WaitConnection {
return fmt . Errorf ( "ошибка отправки пакета на порт %d: %w" , port , err )
@@ -314,7 +321,8 @@ func (pk *PortKnocker) knockTarget(target Target, verbose bool) error {
}
// sendPacket отправляет один пакет на указанный хост и порт
func ( pk * PortKnocker ) sendPacket ( host string , port int , protocol string , waitConnection bool , timeout time . Duration , gateway string ) error {
// sendPacket_backup — резервная копия прежней реализации
func ( pk * PortKnocker ) SendPacket_backup ( host string , port int , protocol string , waitConnection bool , timeout time . Duration , gateway string ) error {
address := net . JoinHostPort ( host , fmt . Sprintf ( "%d" , port ) )
var conn net . Conn
@@ -381,8 +389,8 @@ func (pk *PortKnocker) sendPacket(host string, port int, protocol string, waitCo
return nil
}
// sendPacketWithoutConnection отправляет пакет без установления соединения
func ( pk * PortKnocker ) s endPacketWithoutConnection( host string , port int , protocol string , localAddr net . Addr ) error {
// sendPacketWithoutConnection_backup — резервная копия прежней реализации
func ( pk * PortKnocker ) S endPacketWithoutConnection_backup ( host string , port int , protocol string , localAddr net . Addr ) error {
address := net . JoinHostPort ( host , fmt . Sprintf ( "%d" , port ) )
switch protocol {
@@ -440,6 +448,155 @@ func (pk *PortKnocker) sendPacketWithoutConnection(host string, port int, protoc
return nil
}
// parseGateway возвращает локальные адреса для TCP/UDP и (опционально) имя интерфейса,
// если в gateway передано, например, "eth1". Также поддерживается IP[:port].
func parseGateway ( gateway string ) ( tcpLocal * net . TCPAddr , udpLocal * net . UDPAddr , ifaceName string , err error ) {
gateway = strings . TrimSpace ( gateway )
if gateway == "" {
return nil , nil , "" , nil
}
// Если это похоже на имя интерфейса (буквы/цифры/дефисы, без точного IP)
isIfaceName := regexp . MustCompile ( ` ^[A-Za-z0-9_-]+$ ` ) . MatchString ( gateway ) && net . ParseIP ( gateway ) == nil
if isIfaceName {
// Привязка по интерфейсу. LocalAddr оставим пустым — маршрут выберется ядром,
// а SO_BINDTODEVICE закрепит интерфейс.
return nil , nil , gateway , nil
}
// Иначе трактуем как IP[:port]
host := gateway
if ! strings . Contains ( gateway , ":" ) {
host = gateway + ":0"
}
tcpLocal , err = net . ResolveTCPAddr ( "tcp" , host )
if err != nil {
return nil , nil , "" , fmt . Errorf ( "не удалось разрешить локальный TCP адрес %s: %w" , host , err )
}
udpLocal , err = net . ResolveUDPAddr ( "udp" , host )
if err != nil {
return nil , nil , "" , fmt . Errorf ( "не удалось разрешить локальный UDP адрес %s: %w" , host , err )
}
return tcpLocal , udpLocal , "" , nil
}
// buildDialer создаёт net.Dialer с опциональной привязкой к LocalAddr и интерфейсу (Linux SO_BINDTODEVICE)
func buildDialer ( protocol string , tcpLocal * net . TCPAddr , udpLocal * net . UDPAddr , timeout time . Duration , ifaceName string ) * net . Dialer {
d := & net . Dialer { Timeout : timeout }
if protocol == "tcp" && tcpLocal != nil {
d . LocalAddr = tcpLocal
}
if protocol == "udp" && udpLocal != nil {
d . LocalAddr = udpLocal
}
if strings . TrimSpace ( ifaceName ) != "" {
d . Control = func ( network , address string , c syscall . RawConn ) error {
var ctrlErr error
err := c . Control ( func ( fd uintptr ) {
// Привязка сокета к интерфейсу по имени
ctrlErr = unix . SetsockoptString ( int ( fd ) , unix . SOL_SOCKET , unix . SO_BINDTODEVICE , ifaceName )
} )
if err != nil {
return err
}
return ctrlErr
}
}
return d
}
// sendPacket — обновлённая реализация с поддержкой UDPAddr и SO_BINDTODEVICE
func ( pk * PortKnocker ) sendPacket ( host string , port int , protocol string , waitConnection bool , timeout time . Duration , gateway string ) error {
address := net . JoinHostPort ( host , fmt . Sprintf ( "%d" , port ) )
tcpLocal , udpLocal , ifaceName , err := parseGateway ( gateway )
if err != nil {
return err
}
switch protocol {
case "tcp" :
dialer := buildDialer ( "tcp" , tcpLocal , nil , timeout , ifaceName )
conn , err := dialer . Dial ( "tcp" , address )
if err != nil {
if waitConnection {
return fmt . Errorf ( "не удалось подключиться к %s: %w" , address , err )
}
// без ожидания — пробуем best-effort отправку
return pk . sendPacketWithoutConnection ( host , port , protocol , dialer . LocalAddr )
}
defer conn . Close ( )
_ , err = conn . Write ( [ ] byte { } )
if err != nil {
return fmt . Errorf ( "не удалось отправить пакет: %w" , err )
}
return nil
case "udp" :
dialer := buildDialer ( "udp" , nil , udpLocal , timeout , ifaceName )
conn , err := dialer . Dial ( "udp" , address )
if err != nil {
if waitConnection {
return fmt . Errorf ( "не удалось подключиться к %s: %w" , address , err )
}
return pk . sendPacketWithoutConnection ( host , port , protocol , dialer . LocalAddr )
}
defer conn . Close ( )
_ , err = conn . Write ( [ ] byte { } )
if err != nil {
return fmt . Errorf ( "не удалось отправить пакет: %w" , err )
}
return nil
default :
return fmt . Errorf ( "неподдерживаемый протокол: %s" , protocol )
}
}
// sendPacketWithoutConnection — обновлённая реализация best-effort без ожидания соединения
func ( pk * PortKnocker ) sendPacketWithoutConnection ( host string , port int , protocol string , localAddr net . Addr ) error {
address := net . JoinHostPort ( host , fmt . Sprintf ( "%d" , port ) )
switch protocol {
case "udp" :
// Используем Dialer с коротким таймаутом, локальный адрес может быть *net.UDPAddr
var udpLocal * net . UDPAddr
if la , ok := localAddr . ( * net . UDPAddr ) ; ok {
udpLocal = la
}
d := & net . Dialer { Timeout : 200 * time . Millisecond }
if udpLocal != nil {
d . LocalAddr = udpLocal
}
conn , err := d . Dial ( "udp" , address )
if err != nil {
return nil
} // best-effort
defer conn . Close ( )
_ , err = conn . Write ( [ ] byte { } )
if err != nil {
return nil
}
case "tcp" :
// Короткий таймаут и игнор ошибок
var tcpLocal * net . TCPAddr
if la , ok := localAddr . ( * net . TCPAddr ) ; ok {
tcpLocal = la
}
d := & net . Dialer { Timeout : 100 * time . Millisecond }
if tcpLocal != nil {
d . LocalAddr = tcpLocal
}
conn , err := d . Dial ( "tcp" , address )
if err != nil {
return nil
}
defer conn . Close ( )
_ , _ = conn . Write ( [ ] byte { } )
}
return nil
}
// showEasterEgg показывает забавный ASCII-арт
func ( pk * PortKnocker ) showEasterEgg ( ) {
fmt . Println ( "\n🎯 🎯 🎯 EASTER EGG ACTIVATED! 🎯 🎯 🎯" )