Exchange ne veut plus recevoir de mails

J’ai eu le cas d’un serveur Exchange 2010 qui s’est mis à ne plus recevoir de messages de l’extérieur.
Tout semblait fonctionner :

  • les services étaient tous au vert
  • aucune erreurs ou alertes dans les journaux d’événements
  • les clients pouvaient consulter leurs mails, leurs contacts et leurs taches
  • aucune mise à jour n’a été installé récemment
  • aucun changement de configuration faite récemment

Et pourtant impossible de recevoir et/ou envoyer.
J’ai pu trouver la solution, faute de mieux, en loguant une conversation SMTP avec le serveur Exchange afin de voir ce qu’il se passait réellement, et la bingo : le serveur a répondu 4.3.1 Insufficient System Resources.

Après une courte recherche, il s’est avéré qu’Exchange estimait ne plus avoir suffisamment d’espace disque pour fonctionner convenablement, c’est à dire moins de 4~5 Go de libre. C’est par design, une mesure de sécurité permettant d’avoir un système online en interdisant de dévorer le peu d’espace libre restant.

J’ai ensuite récupéré un peu d’espace disque là où je pouvais, notamment en supprimant des archives laissées par l’installation des cumulative updates Exchange (soit quelques 2Go quand même), ce qui m’a permis de constater le retour à la normal d’Exchange, me laissant ainsi le temps de planifier une petite maintenance pour étendre le disque dur devenu trop petit (facile dans un environnement virtuel ;)).

Voir aussi

IPv6 sous Linux

Autant sous Windows l’IPv6 se configure tout seul, autant sous Linux, c’est une autre histoire, en particulier si ce dernier fait office de routeur IPv6 et qu’on a configuré proprement le pare-feu (ou alors je n’ai rien capté, c’est aussi possible).

Il y a deux aspects à voir, tout d’abord lorsque l’OS sert de routeur IPv6 (qu’il soit ou pas routeur IPv4 n’importe pas), c’est à dire lorsque net.ipv6.conf.default.forwarding = 1 , ce dernier n’écoute plus les Router Advertisement, même si net.ipv6.conf.default.accept_ra = 1  et par conséquent, l’IPv6, le masque de sous-réseau ainsi que les routes sont à configurer manuellement. Cela peut surprendre de prime abord.

Le second aspect se situe au niveau de la découverte de ses voisins. En IPv4, cela se fait avec des paquets ARP, alors qu’en IPv6, cela se fait avec des paquets ICMPv6.
Pour rappel, ARP est un protocole de lien, ce n’est pas encapsulé dans un paquet IP.
D’un point de vue du pare-feu, c’est pratique car alors que iptables ne peux filtrer des paquets ARP, puisque ces derniers n’ont rien à voir avec la couche IP (c’est le rôle d’ebtables), ip6tables sait parfaitement filtrer des paquets ICMPv6, eux-mêmes étant encapsulés dans un paquet IPv6. Mais il faut y penser lorsqu’on active son pare-feu.
Cela donne par exemple pour Gentoo :

# /etc/conf/net
config_eth0="192.168.3.2/24
2001:41d0:fe1c:1300:20c:29ff:fe99:fbf/56"
routes_eth0="
default via 192.168.3.1
"2000::/3 via fe80::5a98:35ff:fea1:a7ea" # ça ne sert à rien de router autre chose que des adresses internet pour un serveur dédié "normal"

La première chose qu’on fait en général avec le pare-feu, c’est de passer la stratégie par défaut à DROP, en ajoutant le minimum syndical, à savoir un accès SSH notamment si on n’est pas en local sur la machine. Mais faire cette manip coupe également le bon fonctionnement de l’IPv6.

Après analyse avec tcpdump des trames réseau, on en vient à la configuration d’un pare-feu stateful suivante :

# attention: on flush toute la table, et on change la règle par défaut afin de rester connecté
ip6tables -P INPUT ACCEPT
ip6tables -F INPUT
ip6tables -A INPUT -m ctstate --ctstate INVALID -j DROP
ip6tables -A INPUT -m ctstate --ctstate RELATED,ESTABLISHED -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -m hl --hl-eq 255 -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -m hl --hl-eq 255 -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
ip6tables -P INPUT DROP
# vos règles d'accès commencent ici

 

Les deux premières règles permettent un minimum de protection contre des paquets éventuellement forgés.
La troisième permet d’autoriser toute connexion sur la boucle locale.

Les règles 4 et 5 sont celles qui font marcher le schmilblick et permet au système de découvrir ses voisins, en particulier l’adresse MAC de sa passerelle vers internet. Il n’est pas possible de filtrer par adresse source, car les requêtes peuvent être légitimement émises par des adresses locales (fe80::/10 ), multicast (ff00:/8 ) ou unicast routables (2000::/3 ), en conséquences on filtre sur le nombre de sauts, qui doit être de 1 avec le filtre hl (hop limit), sachant que dans le cas d’un serveur dédié, seul les réponses et requêtes de la passerelle sont désirées et légitimes.

Compiler les modules vmware sous linux

Afin d’améliorer le fonctionnement et les performances des machines virtuelles, VMware fournit les VMware tools.
Ces  »tools » se décomposent en :

  • une application graphique (wait whaaat ? did you say UI ??)
  • des kernel modules pré-compilés, ainsi que les sources pour les autres noyaux.

J’aurais pu me contenter de fournir les patches, lesquels auraient été suffisant pour compiler les modules, mais en plus d’avoir des codes sources obsolètes, l’installeur s’est mis à dysfonctionner. Impossible donc de compter sur lui pour faire le boulot.

En décortiquant l’installeur, je suis arrivé à un binaire, qui est responsable de la localisation des linux headers sur ma machine, c’est lui qui pour une raison qui m’échappe, ne trouve plus les linux-headers, mais de par sa forme binaire, c’est une voie sans issue (je ne suis pas expert en reverse engineering, et encore moins sous Linux)…

Aucune importance : j’ai trouvé mieux. En décompressant les sources qui se trouvent dans ~/lib/modules/sources , il m’a suffit de lancer un make  et la magie s’est opérée : j’avais mes modules (enfin… après quelques petites modifications) !!

Ce script fonctionne précisément pour les kernels de la branche 3.8 patchés avec grsec. En enlevant les appels à pax_open_kernel  et pax_close_kernel  ou en mettant la bonne pré-condition, on pourrait le rendre compatible avec n’importe quel kernel  de la branche 3.8 je pense.

J’aurais pu fournir les fichiers patch à part, mais j’ai préféré ne garder qu’un seul fichier, afin de conserver une certaine simplicité de gestion pour la maintenance du script.

# sources:
# https://bugs.gentoo.org/attachment.cgi?id=289757&action=edit
# http://pastebin.com/dVPszctW
# https://bugs.gentoo.org/show_bug.cgi?id=461872
mount /mnt/cdrom
tar -xvzf /mnt/cdrom/vmware-tools-distrib.tar.gz /root/
cd /root/vmware-tools-distrib/lib/modules/source

echo compiling vmmemctl...
tar -xvf vmmemctl.tar
cd vmmemctl-only
make
cd ..

echo compiling vmsync
tar -xvf vmsync.tar
cd vmsync-only
echo '
--- driver/sync.c 2013-04-16 21:52:23.277342798 +0200
+++ driver/sync.c 2013-04-16 21:53:43.094056640 +0200
@@ -162,7 +162,7 @@
cancel_delayed_work(&state->thawTask);
list_for_each_safe(cur, tmp, &state->devices) {
dev = list_entry(cur, VmSyncBlockDevice, list);
- if (dev->sb != NULL && dev->sb->s_frozen != SB_UNFROZEN) {
+ if (dev->sb != NULL && dev->sb->s_writers.frozen != SB_UNFROZEN) {
thaw_bdev(dev->bdev, dev->sb);
atomic_dec(&gFreezeCount);
}
@@ -237,7 +237,7 @@
* the superblock is already frozen.
*/
if (inode->i_sb->s_bdev == NULL ||
- inode->i_sb->s_frozen != SB_UNFROZEN) {
+ inode->i_sb->s_writers.frozen != SB_UNFROZEN) {
result = (inode->i_sb->s_bdev == NULL) ? -EINVAL : -EALREADY;
compat_path_release(&nd);
goto exit;
' | patch sync.c
make
cd ..

echo compiling vmci
tar -xvf vmci.tar
cd vmci-only
echo '
--- linux/driver.c 2013-04-17 20:19:38.778514246 +0200
+++ linux/driver.c 2013-04-17 20:43:31.109809776 +0200
@@ -124,7 +124,7 @@
.name = "vmci",
.id_table = vmci_ids,
.probe = vmci_probe_device,
- .remove = __devexit_p(vmci_remove_device),
+ .remove = vmci_remove_device,
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
@@ -252,7 +252,21 @@
#define LinuxDriverUnlockIoctlPerFD(mutex) do {} while (0)
#endif

-static struct file_operations vmuser_fops;
+/* Initialize the file_operations structure */
+static struct file_operations vmuser_fops = {
+ .owner = THIS_MODULE,
+ .poll = LinuxDriverPoll,
+#ifdef HAVE_UNLOCKED_IOCTL
+ .unlocked_ioctl = LinuxDriver_UnlockedIoctl,
+#else
+ .ioctl = LinuxDriver_Ioctl,
+#endif
+#ifdef HAVE_COMPAT_IOCTL
+ .compat_ioctl = LinuxDriver_UnlockedIoctl,
+#endif
+ .open = LinuxDriver_Open,
+ .release = LinuxDriver_Close
+};


/*
@@ -389,26 +403,6 @@
return -ENOMEM;
}

- /*
- * Initialize the file_operations structure. Because this code is always
- * compiled as a module, this is fine to do it here and not in a static
- * initializer.
- */
-
- memset(&vmuser_fops, 0, sizeof vmuser_fops);
- vmuser_fops.owner = THIS_MODULE;
- vmuser_fops.poll = LinuxDriverPoll;
-#ifdef HAVE_UNLOCKED_IOCTL
- vmuser_fops.unlocked_ioctl = LinuxDriver_UnlockedIoctl;
-#else
- vmuser_fops.ioctl = LinuxDriver_Ioctl;
-#endif
-#ifdef HAVE_COMPAT_IOCTL
- vmuser_fops.compat_ioctl = LinuxDriver_UnlockedIoctl;
-#endif
- vmuser_fops.open = LinuxDriver_Open;
- vmuser_fops.release = LinuxDriver_Close;
-
sprintf(linuxState.deviceName, "vmci");
linuxState.major = 10;
linuxState.misc.minor = MISC_DYNAMIC_MINOR;
@@ -1750,7 +1744,7 @@
*-----------------------------------------------------------------------------
*/

-static int __devinit
+static int
vmci_probe_device(struct pci_dev *pdev, // IN: vmci PCI device
const struct pci_device_id *id) // IN: matching device ID
{
@@ -1978,7 +1972,7 @@
*-----------------------------------------------------------------------------
*/

-static void __devexit
+static void
vmci_remove_device(struct pci_dev* pdev)
{
struct vmci_device *dev = pci_get_drvdata(pdev);
' | patch linux/driver.c
make
cd ..

echo compiling vsock
tar -xvf vsock.tar
cd vsock-only
cat ../vmci-only/Module.symvers >> Module.symvers
cp linux/af_vsock.c linux/af_vsock.c.bak
echo '
--- linux/af_vsock.c 2013-04-17 20:25:44.093536289 +0200
+++ linux/af_vsock.c 2013-04-17 20:27:51.518312015 +0200
@@ -3160,11 +3160,15 @@
* else in the future.
*/
for (i = NPROTO - 1; i >= 0; i--) {
- vsockVmciFamilyOps.family = i;
+ pax_open_kernel();
+ *(int *)&vsockVmciFamilyOps.family = i;
+ pax_close_kernel();
err = sock_register(&vsockVmciFamilyOps);
if (err) {
Warning("Could not register address family %d.\n", i);
- vsockVmciFamilyOps.family = VSOCK_INVALID_FAMILY;
+ pax_open_kernel();
+ *(int *)&vsockVmciFamilyOps.family = VSOCK_INVALID_FAMILY;
+ pax_close_kernel();
} else {
vsockVmciDgramOps.family = i;
vsockVmciStreamOps.family = i;
@@ -3202,7 +3206,9 @@
sock_unregister(vsockVmciFamilyOps.family);
}

- vsockVmciDgramOps.family = vsockVmciFamilyOps.family = VSOCK_INVALID_FAMILY;
+ pax_open_kernel();
+ vsockVmciDgramOps.family = *(int*)&vsockVmciFamilyOps.family = VSOCK_INVALID_FAMILY;
+ pax_close_kernel();
vsockVmciStreamOps.family = vsockVmciFamilyOps.family;
}' | patch -l linux/af_sock.c
make
cd ..

if [ -f pvscsi.tar ]
tar -xvf pvscsi.tar
cd pvscsi-only
echo '
--- pvscsi.c 2013-05-14 23:24:04.801773269 +0200
+++ pvscsi.c 2013-05-14 23:25:34.701065554 +0200
@@ -455,7 +455,7 @@
SCSI_SENSE_BUFFERSIZE, PCI_DMA_FROMDEVICE);
}

-static int __devinit pvscsi_allocate_rings(struct pvscsi_adapter *adapter)
+static int pvscsi_allocate_rings(struct pvscsi_adapter *adapter)
{
adapter->rings_state = pci_alloc_consistent(adapter->dev, PAGE_SIZE,
&adapter->ringStatePA);
@@ -1234,7 +1234,7 @@
* just use a statically allocated scatter list.
*
*/
-static int __devinit pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
+static int pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
{
struct pvscsi_ctx *ctx;
int i;
@@ -1317,7 +1317,7 @@
return numPhys;
}

-static int __devinit pvscsi_probe(struct pci_dev *pdev,
+static int pvscsi_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct pvscsi_adapter *adapter;
@@ -1535,7 +1535,7 @@
.name = "pvscsi",
.id_table = pvscsi_pci_tbl,
.probe = pvscsi_probe,
- .remove = __devexit_p(pvscsi_remove),
+ .remove = pvscsi_remove,
COMPAT_PCI_SHUTDOWN(pvscsi_shutdown)
};' | patch pvscsi.c
make
cd ..
fi

echo installing
LIBDIR=/lib/modules/`uname -r`/misc
[ -d "$LIBDIR" ] || mkdir "$LIBDIR"
cp vmmemctl.o "$LIBDIR/vmmemctl.ko"
cp vmsync.o "$LIBDIR/vmsync.ko"
cp vmci.o "$LIBDIR/vmci.ko"
cp vsock.o "$LIBDIR/vsock.ko"
[ -f pvscsi.o ] && cp pvscsi.o "$LIBDIR/pvscsi.o"

// clean up
rm -R vmci-only vmsync-only vsock-only vmmemctl-only
[ -d pvscsi-only ] && rm -R pvscsi-only

// finished !
# refresh modules.dep.bin for modprobe
depmod

echo "You just have to add this line on /etc/conf.d/modules to auto-load the vmware modules"
echo "module_$(uname -r | sed -e 's/[-.]/_/g')=\"vmmemctl vmsync vmci vsock pvscsi\""

Le petit plus du script : il évite d’installer les VMware tools. Sans interface graphique, ils ne servent à rien, et sous Gentoo, ça pollue la sortie de revdep-rebuild .

Ce script ne compile pas les modules vmxnet  ni vmxnet3 , le premier parce qu’il est déprécié et parce que je ne l’utilise pas, le second parce qu’il est déjà intégré au noyau linux.
Dans des versions ultérieures de linux, il se pourrait que je n’ai même plus à gérer la compilation de vmci  et vsock  : en effet ces derniers pourraient faire une entrée dans le noyau linux.

Notes

J’ai finis de rédiger le script hier soir en compilant mes commandes balancées dans le terminal. Je n’ai jamais testé le script, donc à vos risques et périls !
En revanche, je peux prendre en compte vos remarques.