Páginas

Mostrando entradas con la etiqueta sh. Mostrar todas las entradas
Mostrando entradas con la etiqueta sh. Mostrar todas las entradas

lunes, 6 de abril de 2026

Configuración de LAGG failover en FreeBSD 14.4

LAGG failover en FreeBSD 14.4 em0 (primaria) y wlan0 (respaldo via iwm0)

Fuente:


https://www.ccammack.com/posts/configure-a-wifi-client-on-freebsd
https://wiki.freebsd.org/Devd
https://headthirst.com/freebsd-suspend.html
https://forums.freebsd.org/threads/freebsd-command-line-wifi-manager.89090/
https://www.dwarmstrong.org/freebsd-network-laptop/
https://libreboot.org/docs/bsd/suspend-resume-speedup.html
https://forums.freebsd.org/threads/freebsd-command-line-wifi-manager.89090/
https://forums.freebsd.org/threads/wifi-stops-working-after-suspend-resume.85889/
https://www.reddit.com/r/voidlinux/comments/1lfxurz/wifi_disappears_after_waking_from_suspend_i

Nombres de los dispositivos 802.11 disponibles

sysctl net.wlan.devices
net.wlan.devices: iwm0

Tomando como ejemplo mi computadora portátil (Dell Latitude 7390), la interfaz Ethernet em0 funcionará como puerto maestro y la interfaz inalámbrica wlan0 será el dispositivo de respaldo. Combino las dos interfaces en una interfaz virtual sobrescribiendo la dirección MAC de la interfaz Ethernet con la dirección MAC de la interfaz inalámbrica.

Determinar la dirección MAC de la interfaz inalámbrica

ifconfig wlan0 | grep ether
	ether 18:56:80:f8:98:e6

Cambiar la dirección MAC de la interfaz Ethernet para que coincidan

ifconfig em0 ether 18:56:80:f8:98:e6	

/etc/rc.conf

...
ifconfig_em0="ether 18:56:80:f8:98:e6"
wlans_iwm0="wlan0"
ifconfig_wlan0="WPA"
cloned_interfaces="lagg0"
ifconfig_lagg0="laggproto failover laggport em0 laggport wlan0 SYNCDHCP"
create_args_wlan0="country ES"
defaultrouter="192.168.88.1"
defaultroute_delay="10"
background_dhclient_lagg0="YES"
... 
Archivo /etc/wpa_supplicant
/etc/wpa_supplicant.conf
ctrl_interface=/var/run/wpa_supplicant
eapol_version=2
fast_reauth=1

network={
	ssid="MikroTik-39F9C0"	
        psk=755862a5b1a4ef59e2e62590677a75f5317193637a8629dccb18c40e8634a68e
}

Reiniciar

Estado general del LAGG

ifconfig lagg0
lagg0: flags=1008843 UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,LOWER_UP metric 0 mtu 1500
	options=0
	ether 18:56:80:f8:98:e6
	hwaddr 00:00:00:00:00:00
	inet 192.168.88.50 netmask 0xffffff00 broadcast 192.168.88.255
	laggproto failover lagghash l2,l3,l4
	laggport: em0 flags=1 MASTER
	laggport: wlan0 flags=4 ACTIVE
	groups: lagg
	media: Ethernet autoselect
	status: active
	nd6 options=29 PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL

Ver puerto activo

ifconfig lagg0 | grep -E "laggport|status"
	laggport: em0 flags=1 MASTER 
	laggport: wlan0 flags=4 ACTIVE
	status: active

Estadísticas en detalle

sysctl net.link.lagg
net.link.lagg.lacp.default_strict_mode: 1
net.link.lagg.lacp.debug: 0
net.link.lagg.default_flowid_shift: 16
net.link.lagg.default_use_numa: 1
net.link.lagg.default_use_flowid: 0
net.link.lagg.failover_rx_all: 0

En /etc/rc.conf em0 no debe tener IP propia, es gestionado por lagg0 wlan0 no debe tener DHCP propio cuando está bajo lagg0; el DHCP lo gestiona lagg0. El failover es automático, al detectar pérdida de carrier en em0, el tráfico pasa a wlan0 sin intervención manual.

Sin conexión a Internet después de salir de hibernación

Al salir de hibernación, devd no recrea lagg0 de forma automática. La solución es un script de resume que se ejecute al despertar.

Crear el script de resume-network.sh

/usr/local/sbin/resume-network.sh

#!/bin/sh
sleep 3

# Destruir interfaces
ifconfig lagg0 destroy 2>/dev/null
ifconfig wlan0 destroy 2>/dev/null

# Recrear wlan0 sin country (lo hereda de rc.conf al crear)
ifconfig wlan0 create wlandev iwm0
ifconfig wlan0 country ES 2>/dev/null

# Recrear lagg0
ifconfig lagg0 create
ifconfig lagg0 laggproto failover
ifconfig lagg0 laggport em0
ifconfig lagg0 laggport wlan0
ifconfig lagg0 up

# Arrancar wpa_supplicant
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf

# Esperar asociacion (max 30s)
i=0
while [ $i -lt 30 ]; do
    status=$(ifconfig wlan0 | grep "status:" | awk '{print $2}')
    [ "$status" = "associated" ] && break
    sleep 1
    i=$((i + 1))
done

logger "resume-network: wlan0 status=$status tras ${i}s"

# Matar dhclient previo si existe
pkill -F /var/run/dhclient/dhclient.lagg0.pid 2>/dev/null
sleep 1

# Obtener IP
dhclient lagg0

# Ruta por defecto si dhclient no la puso
sleep 2
netstat -rn | grep -q "^default" || route add default 192.168.88.1

logger "resume-network: completado"

Aumentar timeout de dhclient

vim /etc/dhclient.conf
interface "lagg0" {
    timeout 60;
    retry 10;
}

Configurar devd para se ejecute al despertar

vim /etc/devd/resume-network.conf
notify 100 {
    match "system"      "ACPI";
    match "subsystem"   "Resume";
    action "/usr/local/sbin/resume-network.sh &";
};

Reinicar devd

sudo service devd restart

Comprobar que devd capta el evento revisando tras salir de hibernación

tail -5 /var/log/messages
Apr  5 16:11:50 solaris dhclient[13642]: New IP Address (lagg0): 192.168.88.50
Apr  5 16:11:50 solaris dhclient[13646]: New Subnet Mask (lagg0): 255.255.255.0
Apr  5 16:11:50 solaris dhclient[13650]: New Broadcast Address (lagg0): 192.168.88.255
Apr  5 16:11:50 solaris dhclient[13654]: New Routers (lagg0): 192.168.88.1
Apr  5 16:11:51 solaris root[13671]: resume-network: completado

Al hibernar, FreeBSD destruye las interfaces clonadas (lagg0) y en algunos casos también wlan0. Al despertar, rc.conf no se reprocesa. devd es el mecanismo correcto para reaccionar a eventos ACPI como Resume.

El sleep 3 inicial es importante porque iwm0 necesita unos segundos para que el firmware del adaptador WiFi Intel esté operativo tras el resume.

Ver si devd captó el evento Resume

grep -E "Resume|resume-network" /var/log/messages | tail -5

Apr  5 15:48:07 solaris carlos[12362]: resume-network: wlan0 status=associated tras 3s
Apr  5 15:48:09 solaris carlos[12527]: resume-network: completado
Apr  5 16:11:39 solaris kernel: rtsx0: Resume
Apr  5 16:11:49 solaris root[13506]: resume-network: wlan0 status=associated tras 3s
Apr  5 16:11:51 solaris root[13671]: resume-network: completado

Comprobar que dhclient obtuvo IP

Apr  5 16:11:50 solaris dhclient[27343]: My address (192.168.88.50) was deleted, dhclient exiting
Apr  5 16:11:51 solaris dhclient[28339]: dhclient already running, pid: 28314.
Apr  5 16:11:51 solaris dhclient[28339]: exiting.
Apr  5 16:11:52 solaris dhclient[28317]: connection closed
Apr  5 16:11:52 solaris dhclient[28317]: exiting.
Apr  5 16:11:54 solaris dhclient[28659]: New IP Address (lagg0): 192.168.88.50
Apr  5 16:11:54 solaris dhclient[28663]: New Subnet Mask (lagg0): 255.255.255.0
Apr  5 16:11:55 solaris dhclient[28667]: New Broadcast Address (lagg0): 192.168.88.255
Apr  5 16:11:55 solaris dhclient[28671]: New Routers (lagg0): 192.168.88.1
FreeBSD es genial!.

martes, 15 de abril de 2025

Script sh Replicar Instantáneas de Conjunto de Datos ZFS desde Servidor FreeBSD Local a otra Máquina FreeBSD Externa

Fuente:

https://www.iceflatline.com/2015/07/using-zfs-replication-features-in-freebsd-to-improve-my-offsite-backups/

El autor de este sencillo e ingenioso guión es iceflatline. Contiene algunas modificaciones para adaptarlo a este laboratorio

https://gnulinuxcodigo.blogspot.com/2025/03/crear-particiones-en-disco-duro-usb-con.html

Crear el pool de respaldo zbackup

 zpool create -o ashift=12 \
-O compression=lz4 \
-O atime=off \
-O mountpoint=none \
backup /dev/gpt/zbackup

Conjuntos de datos zbackup

 sudo zfs create -o mountpoint=none zbackup/solaris
 sudo zfs create -o mountpoint=none zbackup/solaris/home
 sudo zfs create -o mountpoint=none zbackup/solaris/home/carlos
 sudo zfs create -o mountpoint=none zbackup/tormenta-confiles
 sudo zfs create -o mountpoint=none zbackup/usr/
 sudo zfs create -o mountpoint=none zbackup/usr/backup
 sudo zfs create -o mountpoint=none zbackup/usr/backup/docs
 sudo zfs create -o mountpoint=none zbackup/usr/home

Flujo de trabajo recomendado para un disco de respaldo mountpoint=none

Seguridad contra modificaciones accidentales

Evita conflictos de rutas

Comportamiento en el arranque — Con mountpoint=none ZFS no intenta montar nada del pool zbackup al arrancar, lo que acelera el boot y evita errores si el disco USB no está conectado en ese momento.

Acceso bajo demanda — Cuando se necesita recuperar algo, se monta puntualmente el dataset de interés, se hace lo que se necesita, y luego se desmonta.

Los scripts no se ven afectados — zfs receive escribe directamente al dataset sin necesidad de que esté montado. mountpoint=none no interfiere en absoluto con zfs send/receive.

carlos@tormenta:~ % zfs list -r zbackup
zbackup                            48.9G  1.71T  4.86M  none
zbackup/solaris                    40.8G  1.71T    96K  none
zbackup/solaris/home               40.8G  1.71T    96K  none
zbackup/solaris/home/carlos        40.8G  1.71T  39.9G  none
zbackup/tormenta-confiles           800K  1.71T   800K  none
zbackup/usr                        8.16G  1.71T   104K  none
zbackup/usr/backup                 7.80G  1.71T   104K  none
zbackup/usr/backup/docs            7.80G  1.71T  7.80G  none
zbackup/usr/home                    368M  1.71T   132M  none
#!/bin/sh
### BEGIN INFO
# PROVIDE:
# REQUIRE:
# KEYWORD:

### INICIO DEL GUION
# Estas variables se nombran primero porque están anidadas en otras variables.
snap_prefix=snap
retention=15

# Se necesitan rutas completas a estas utilidades al ejecutar el script desde \
cron.
date=/bin/date
grep=/usr/bin/grep
sed=/usr/bin/sed
sort=/usr/bin/sort
xargs=/usr/bin/xargs
zfs=/sbin/zfs

src_0="zroot/home/carlos"
dst_0="zbackup/solaris/home/carlos"
host="root@192.168.88.160"

# Usar $date en lugar de date para consistencia con rutas absolutas.
today="$snap_prefix-`$date +%Y%m%d`"
yesterday="$snap_prefix-`$date -v -1d +%Y%m%d`"
snap_today="$src_0@$today"
snap_yesterday="$src_0@$yesterday"

# Patrón grep con sufijo numérico para evitar falsos positivos.
snap_old=`$zfs list -t snapshot -o name \
  | $grep "${src_0}@${snap_prefix}-[0-9]" \
  | $sort -r \
  | $sed 1,${retention}d \
  | $sort \
  | $xargs -n 1`

log=/home/carlos/cronlog

# Todo el pipeline de listado remoto corre en tormenta vía SSH.
snap_old_dst_0=`ssh $host "$zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_prefix}-[0-9]' \
  | sort -r \
  | sed 1,${retention}d \
  | sort"`

# Crea una línea en blanco entre la entrada de registro anterior y esta.
echo >> $log

# Imprimir el nombre del script.
echo "zfsrep.sh" >> $log
# Imprime la fecha de hoy.
$date >> $log
# Crea una línea en blanco.
echo >> $log

# Buscar instantánea de hoy y, si no la encuentra, créala.
if $zfs list -H -o name -t snapshot \
  | $sort \
  | $grep "${snap_today}$" > /dev/null
then
	echo "La instantánea de hoy '$snap_today' ya existe." >> $log
else
	echo "Tomando la instantánea de hoy: $snap_today" >> $log
	$zfs snapshot -r $snap_today >> $log 2>&1
fi

echo >> $log

# Replicar el snapshot diario (ayer) o mostrar error si falta.
if $zfs list -H -o name -t snapshot \
  | $sort \
  | $grep "${snap_yesterday}$" > /dev/null
then
	echo "La instantánea de ayer '$snap_yesterday' existe. Continuando \
    con la replicacion..." >> $log
	$zfs send -R -i $snap_yesterday $snap_today \
	  | ssh $host $zfs receive -vuF -x mountpoint $dst_0 >> $log 2>&1
	echo >> $log
	echo "Replicacion completada." >> $log
else
	echo "Error: Replicacion no completada. Falta la instantánea de \
    ayer." >> $log
fi

echo >> $log

# Eliminar instantáneas locales más antiguas que el valor asignado a retention.
echo "Intentando destruir instantáneas antiguas $src_0..." >> $log

if [ -n "$snap_old" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas:" >> $log
	echo "$snap_old" >> $log
	$zfs list -t snapshot -o name \
	  | $grep "${src_0}@${snap_prefix}-[0-9]" \
	  | $sort -r \
	  | $sed 1,${retention}d \
	  | $sort \
	  | $xargs -n 1 $zfs destroy -r >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea local para \
    destruir." >> $log
fi

echo >> $log

# Purgar snaps remotos > retention
# Todo el pipeline, incluido zfs destroy, corre en tormenta vía SSH.
echo "Intentando destruir instantáneas antiguas $dst_0..." >> $log

if [ -n "$snap_old_dst_0" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas en $host:" >> $log
	echo "$snap_old_dst_0" >> $log
	ssh $host "$zfs list -t snapshot -o name \
	  | grep '${dst_0}@${snap_prefix}-[0-9]' \
	  | sort -r \
	  | sed 1,${retention}d \
	  | sort \
	  | xargs -n 1 $zfs destroy -r" >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea remota para \
    destruir." >> $log
fi

# Marcar el fin del script con un delimitador.
echo "**********" >> $log

### FIN DEL SCRIPT

Luego, agregue una tarea a la tabla cron bajo la cuenta del usuario. El script se ejecuta todos los días a las 22:00 (hora local):

# Ejecuta script backup cada día a las 22:00
0 22 * * * /home/carlos/bin/zfsrep.sh

Las entradas del archivo /home/carlos/cronlog se verán así:

**********

zfsrep.sh
Tue Apr 15 09:45:19 CEST 2025

Tomando la instantánea de hoy: zroot/home@snap-20250415

La instantánea de ayer 'zroot/home@snap-20250414' existe. \
Continuando con la replicacion...
receiving incremental stream of zroot/home@snap-20250415 into \
zbackup/solaris/home/carlos@snap-20250415
received 312B stream in 0.91 seconds (341B/sec)
receiving incremental stream of zroot/home/carlos@snap-20250415 \
into zbackup/solaris/home/carlos@snap-20250415
received 16.3M stream in 7.07 seconds (2.30M/sec)

Replicacion completada.

Intentando destruir instantáneas antiguas zroot/home...
Destruyendo las siguientes instantáneas antiguas:
zroot/home@snap-20250327

Intentando destruir instantáneas antiguas zbackup/solaris...
No se pudo encontrar ninguna instantánea para destruir.
FreeBSD es genial!.