Skip to content

Conservative Enterprise Linux Distributions might hurt us

The argument is clear, by conservatively patching packages (backporting fixes) systems with a Enterprise Linux contract can be held stable and secure for a long time. But to what cost (besides the support contract) this comes?

I noticed that even with the latest SUSE Linux Enterprise 15 SP5 you get a OpenSSH version which does not support hybrid post-quantum key exchanges, and even worse it has SHA1 and the old rsa-ssh key type enabled, as well as the weak DH Group14 or UMAC-64 and non-etm MACs. This openssh 8.4 clone is just not secure by default anymore.

It would be hard for a company on a Enterprise Linux trip to recompiler their own openssh (in order to get the PQC) or to harden the crypto settings. In this regard RHEL with its system wide crypto policy is a bit better, but its dubious if you can set it system wide to "FUTURE" crypto protection level just to get rid of the worst offenders.

Of course it could be argued that nobody really is endangered by those weak algorithms, especially not if the SSH connections are not used on the trusted Internet. But then again - why take the risk. We can only conclude - if you need the additional security, you will need to harden even your expensice supported enterprise systems.

Debian Archiv Signaturprüfung

Debian hat schon seit Ewigkeiten von der Prüfung von Paketsignaturen auf die Validierung von ganzen Archiven umgestellt. Das ist zum einen performanter weil nur die Signatur beim Update der Repodaten geprüft werden muss, und zum anderen prüft das die Zusammenstellung aller Versionen von Paketen im Gegensatz zur Gültigkeit einzelner Pakete, was ja für „Downgrade Angriffe“ verwundbar ist.

Es funktioniert so, dass APT eine InRelease Datei aus dem Repository holt, und diese Datei ist mit dem Archivschlüssel PGP signiert. Nach der Prüfung der Signatur gegen bekannte Debian Schlüssel wird dann anhand der darin enthaltenen Prüfsumme das Paketfile heruntergeladen (und die Püfsumme stellt sicher, dass es das erwartete File ist). Dieses wiederum enthält eine Liste aller Pakete mit deren Prüfsummen. Wird über diesen Weg ein Paket geladen, dann ist es implizit von der Archivsignatur abgedeckt,

In einem Docker Image von Debian werden die bekannten Debian Archivschlüssel ausgeliefert und als trusted konfiguriert. Ich wollte jetzt die Schlüssel der älteren Distributionen entfernen und habe das Verzeichnis /etc/apt/trusted-pgp.d/ geleert. Dieses enthält laut der man page die Schlüssel mit denen geprüft wird.

Ein schneller Test mit „apt update“ hat mich dann aber ernüchtert: die Release files des Repositories für bookworm, bookworm-updates und bookworm-security wurden anstandslos refreshed. Mit etwas rumsuchen hab ich dann im Image ein zweites Verzeichnis von keyrings gefunden, und tatsächlich wenn man diese entfernt schlägt der update fehl.

Es war dann aber eine kleine Abenteurreise in die Untiefen von APT config (und apt-key und apt-secure(8) die alle teilweise veraltet sind) bis ich dann feststellte es liegt an der ausgelieferten /etc/apt/sources.d/debian.sources, da dort nicht nur die Paketquellen für die 3 Komponenten konfiguriert sind, sondern auch der keyring der zur Signaturprüfung verwendet werden soll. Und der zeigt auf /usr/share/keyrings/debian-archive-keyrings.gpg, was wiederum eine Sammlung aller (auch alten) Archiv keys ist.

Man kann jetzt entweder die „Signed-By“ Zeilen entfernen und das trusted-pgp.d aufräumen, oder man spezifiziert nicht den sammel-keyring, sondern einen der nur für die aktuelle Distribution gilt (wird aber nicht ausgeliefert). Noch kleinlicher ist es dann den genauen keying pro Komponente. Die beiden Suites bookworm-updates und bookworm-security können aber nicht mit einem gemeinsamen key geprüft werden, müssen also in 2 Sektionen im File aufgeteilt werden. Das sieht dann so aus:

Types: deb
URIs: http://deb.debian.org/debian
Suites: stable
Components: main
Signed-By: /usr/share/keyrings/debian-archive-bookworm-stable.gpg

Types: deb
URIs: http://deb.debian.org/debian
Suites: stable-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-bookworm-automatic.gpg

Types: deb
URIs: http://deb.debian.org/debian-security
Suites: stable-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-bookworm-security-automatic.gpg 

Ich hab das mal hier im Project das die £cripte betreut gemeldet.

Windows 11 evaluation VM unter Windows 10 Hyper–V

Es gibt ein paar Blogs zum Thema Windows 11 in einer Windows 10 Hyper-V VM (unter Windows 10) zu installieren. Falls die Systemvoraussetzungen nicht erfüllt sind, wird die folgende Meldung angezeigt:

This PC can’t run Windows 11

In dem Fall müssen die folgenden Vorraussetzungen stimmen:

  • Gen2 VM
  • mindestens 2 virtuelle Prozessoren zugewiesen
  • Secure Boot mit der Microsoft Windows Zertifizikatsvorlage
  • aktiviertes VMTPM (kann nach der Installation abgeschalten werden). Bei älteren Windows 10 (vor dem Aniversary Update) muss man dazu wohl auch den Isolation Mode aktivieren.
  • mindestens 3600MB Speicher und dynamischer Mindestspeicher (oder dynamisch abschalten für die Installation)
  • 60GB verfügbare Größe für die VHDX Disk (der 127GB default ist auch OK)

Getestet habe ich das mit einem Windows 11 Enterprise Evaluation ISO. Eine einfachere Option ist den Schnellstart Assistenten im Hyper-V Manager zu verwenden und dort die Windows 11 Developer Evaluation zu verwenden. Diese läd eine angepasste VM automatisch herunter.

The Lampster Bluetooth Details (BLE)

Yellow super-hero figurine in A-pose with tractor lamp head as desk lamp, shining red/blue RGB light.

Only 6 years after my wife gifted me with a Lampster from Kickstarter as X-Mass present the device finally arrived.

I may post a review here, but today I just wanted to write down what I have found out about its BLE control.

I used the LightBlue iPad App to discover services and tried out a few settings.

Lampster has one service with the UUID B8EC7DF5-DB9A-B2Bf-F4A9-93DBA36C7BC2 This service has multiple properties, the following I reversed:
Manufacturer Name: "The Lampster, the licitatie"
Model Number: "LA-2017B"
Serial Number: "15-....-6555"
Hardware Revision: 100B
Firmware: 10
Software Revision: 0.1.0

First Characteristic(?) 01FF5553-

Property 1 (Mode?) 01FF5554-BA5E-F4EE-5CA1-EB1E5E4B1CE0
1 Byte: On/Off in White and/or RGB Mode
Bit 7 = on/off, Bit 6 = White, Bit 5 = RGB, Bit 4 = Off on Disconnect (0-3 always zero)
0xA0 = RGB, 0xC0 = White, 0xE0 = Both, 0x20 = Off, next RGB, 0x40 = Off, next White, 0x60 = off, next both

Property 2 (White Levels?) 01FF5556-BA5E-F4EE-5CA1-EB1E5E4B1CE0 (if mode = C0)
2 Byte 0xwwcc ww = 0x00-0x64 warm white (percentage), cc = 0x00 - 0x64 cold white (percentage)

Property 3 (RGB) 01FF5559-BA5E-F4EE-5CA1-EB1E5E4B1CE0 (if mode = A0)
3 Byte 0xrrggbb rr = 0x00 -0x64 red, gg = 0x00 - 0x64 green, bb = 0x00 - 0x64 blue (percentage)
There are 5 more properties, maybe they are related to timer and demo mode? Second Characteristic(?) 01FF5550- write only (indicate, without response) properties 01FF5551- and 01FF5552-. Both seems to disconnect and turn off the light (until reconnect).

javax.net.debug=all with OpenJDK 8u251++ (TLSv1.3 backport)

Since the new JSSE version was backported to OpenJDK (JDK-8248721) to support TLSv1.3 the logic of TLS debug traces has changed a bit. If you use the old -Djavax.net.debug=all system property syntax, the trace messages will be written to stdout of the process, but not to the Java System.out (redirected) stream. This means, it will not show app in the application server logfiles (like Wildfly/JBoss).

The new debug logger will actually use a configured java.util.logger (JUL) logger when you specify the system property empty (without an value: -Djavax.net.debug). In this case the log messages will be ready for a application container to log via a LogHandler - for example an internal logging bridge. The logger name will be javax.net.ssl and all with level FINE or lower.

The mechanism works with Zulu and AdoptOpenJDK, the Oracle Java binaries seem to have some problems in this are, as reported by me on jdk8u-dev mailing list.

I managed to get JBoss to log the messages with specifying a new category javax.net.ssl with level ALL and specifying the -Djavax.ssl.debug system property, however in my case the log messages are missing the hex dump attachments. This is because the log records contain an additional string parameter without a format parameter and the JBoss logmanager does not formating those excessive (multi line) strings at the end of the log message. You would probably have to extend logmanager to do that (which might be good for malformed log format strings in the first place).

Asynchrones Laden in Guava Cache

Google Guava Cache ist eine weitverbreitete Komponente zur Implementierung von caches. Sie bietet ein Framework um schnell in-memory caches zu implementieren. Unter anderem erlaubt ein CacheLoader den asynchronen refresh von Einträgen. Dabei muss man aber beachten, dass dies nicht out-of-the-box geschieht, sondern dass man dazu eine eigene Logik implementieren muss die das refreshen/laden im Hintergrund implementiert.

Die Grundlagen sind im Guava Wiki beschreiben, ich hab aber aktuell ein Issue offen in dem ich die Dokumentation einer bestehenden Hilfsmethode vorschlage:

Ein kleiner Wrapper wird von Google seit Guava 17 bereitgestellt, man kann damit einen synchronen CacheLoader der keine besondere async-logik enthält wrappen, so dass die refreshes an einen Executor delegiert werden: CacheLoader.asyncRefresh().

Damit lässt sich schnell aus einem vorhandenen CacheLoader ein asynchroner machen. Dabei ist zu beachten dass der Wrapper zusätzliche Objekte erstellt bei jedem re-fresh. Wenn es also auf leichtgewichtige Implementierung ankommt, und wenn eventuell ein Teil des Refresh codes synchron laufen kann, so ist es besser die Logik selbst zu implementieren (man muss nur darauf achten die Erstellung der Future erfolgt synchron, alles was bis zur completion ausgeführt wird läuft im Hintergrund und verzögert keine Lookups).

Folgende Eigenheiten hat das Refreshing:

  • Wenn das Refreshing mittels cache.refresh("key") angestoßen wird, so wartet der refresh Aufruf bis der CacheLoader fertig ist (genauer gesagt, bis die reload future zurückgegeben wurde). Das blockiert nur den Aufrufer-thread, alle anderen Threads die in der Zwischenzeit Cache lookups (für den gleichen Key) machen, bekommen den noch gecachten (alten) Wert.
  • Wenn ein Cache invalidiert wird oder ein Wert aus dem Cache expired, so gibt es keinen alten bestehenden Wert und jeder folgende lookup wird auf das Ende des CacheLoader.load warten müssen. Dabei wird der load pro Key nur einmal gestartet und alle anderen Threads die in dem Zeitraum den Wert anfordern werden angehalten - das ist gut da es die Parallelität des CacheLoader anfragen limitiert. Diese Methode wird nicht in einer asynchronen Variante gewrapped.
  • Wenn refreshing nicht wegen einem direkten refresh(key) Aufruf notwendig wird sondern durch ablaufen des refreshAfterWrite() Zeitraums, so wird das Refreshing durch den nächsten lookup angestoßen. Wenn der CacheLoader synchron ist wird dies auch den (zufällig nächsten) lookup Thread so lange blockieren. Alle weiteren Lookups werden nicht blockiert sondern bekommen den alten Wert. D.h. man muss hier mit regelmäßigen einzelnen verzögerten Lookups rechnen und man kann nicht davon ausgehen dass die Anfragereihenfolge klar in "alte Werte - neue Werte" sortiert werden kann.

The Porting to 11 Theory - The QName Distraction

If you have been using Java for quite some time (since 1.4) and wanted to maintain binary data compatibilitiy, chances are high, that you are relying on the com.sun.xml.namespace.QName.useCompatibleSerialVersionUID=1.0 system property. This will ensure your JVM creates and reads serialized javax.xml.namespace.QName Objects with the same serialisation version UID compatibleSerialVersionUID = 4418622981026545151L. This is especially needed, if you use serialization to store or transmit object trees containing XML objects (and you did not serialize the XML to actual text).

The actual serialVersionUID of QName is meanwhile -9120448754896609940L, but with the compatibility hack you did not only generate objects with the compatible version, but also accepted them.

When you move to Java beyond 8 however, this might break. The backward compatible logic has been removed in JAXP of Java 11. This means your QName instances saved with a not so old Java runtime of 8 (but with the compatibility flag set), suddenly do not work anymore.

Bummer. This is one of the reasons why it is important to follow up on such compatibility hacks and fix them for good. Otherwise it will bite you when you port to newer Java versions just a few decades later.

In JDK11 the compatibility function was removed, with a somewhat sarcastic comment:

  // tests show that the ID is the same from JDK 1.5 through JDK 9
  private static final long serialVersionUID = -9120448754896609940L;

Certainly JDK 1.5 up until 10 did use the default UID, but not if you had used the compatibility flag the whole time.

So the following test code will demonstrates the servialVersionUIDs returned by different Java versions, with and without the compat flag:

Runtime: OpenJDK 64-Bit Server VM 1.8.0_212-b04/25.212-b04 on Windows 10 10.0
 com.sun.xml.namespace.QName.useCompatibleSerialVersionUID=1.0
Created QName: {http://eckenfels.net}bernd  with UID 4418622981026545151
Serialized: rO0ABXNyABlqYXZheC54bWwubmFtZXNwYWNlLlFOYW1lPVIaMLx2_f8CAA...dAAA

Runtime: OpenJDK 64-Bit Server VM 1.8.0_212-b04/25.212-b04 on Windows 10 10.0
 com.sun.xml.namespace.QName.useCompatibleSerialVersionUID=N/A
Created QName: {http://eckenfels.net}bernd  with UID -9120448754896609940
Serialized: rO0ABXNyABlqYXZheC54bWwubmFtZXNwYWNlLlFOYW1lgW2oLfw73WwCAA...dAAA


Runtime: OpenJDK 64-Bit Server VM 11.0.1+13-LTS/11.0.1+13-LTS on Windows 10 10.0
 com.sun.xml.namespace.QName.useCompatibleSerialVersionUID=N/A
Created QName: {http://eckenfels.net}bernd  with UID -9120448754896609940
Serialized: rO0ABXNyABlqYXZheC54bWwubmFtZXNwYWNlLlFOYW1lgW2oLfw73WwCAA...dAAA

Runtime: OpenJDK 64-Bit Server VM 11.0.1+13-LTS/11.0.1+13-LTS on Windows 10 10.0
 com.sun.xml.namespace.QName.useCompatibleSerialVersionUID=1.0
Created QName: {http://eckenfels.net}bernd  with UID -9120448754896609940
Serialized: rO0ABXNyABlqYXZheC54bWwubmFtZXNwYWNlLlFOYW1lgW2oLfw73WwCAA...dAAA

As you can see, on the JDK11 the compat flag is ignored: only the new default serialVersion UID is used.

With a little modified ObjectInputStream (and there are many reasons to have a custom OIS, like this replacement, but also for hooking into modularized classloaders or doing object filtering) all variantes can be read without the need for a compat flag:

Runtime: OpenJDK 64-Bit Server VM 1.8.0_212-b04/25.212-b04 on Windows 10 10.0
 com.sun.xml.namespace.QName.useCompatibleSerialVersionUID=1.0

Deserialize Old
 Default OIS read: {http://eckenfels.net}bernd
 My OIS read: {http://eckenfels.net}bernd

Deserialize Default
 Default OIS failed: java.io.InvalidClassException: javax.xml.namespace.QName; local class incompatible: stream classdesc serialVersionUID = -9120448754896609940, local class serialVersionUID = 4418622981026545151
 My OIS read: {http://eckenfels.net}bernd


Runtime: OpenJDK 64-Bit Server VM 11.0.1+13-LTS/11.0.1+13-LTS on Windows 10 10.0
 com.sun.xml.namespace.QName.useCompatibleSerialVersionUID=N/A

Deserialize Old
 Default OIS failed: java.io.InvalidClassException: javax.xml.namespace.QName; local class incompatible: stream classdesc serialVersionUID = 4418622981026545151, local class serialVersionUID = -9120448754896609940
 My OIS read: {http://eckenfels.net}bernd

Deserialize Default
 Default OIS read: {http://eckenfels.net}bernd
 My OIS read: {http://eckenfels.net}bernd

The code for this tester can be found in a GitHub Gist (including test vectors and complete sample output).

JNDI LDAP with Active Directory with Signing

If you have used JNDI to connect to a Microsoft Active Directory LDAP Server you might see the Warning Event 2886 of source ActiveDirectory_DomainService (every 24 hours) telling you, that you should turn on LDAP Signing. This is also strongly recommended by Microsoft in their latest Security Advisory. They also will turn this on by default in the March 2020 (was January) Windows Server update.

I am not sure if this will actually happen, since a lot of legacy LDAP clients might need to be fixed first. I specifically had a look at Java with the LDAP Naming provider. (Update: as predicted Microsoft has postponed this for now).

If you use a simple bind or a default DIGEST-MD5 with no TLS (LDAPs) and no integrity or confidentiality protection you get the following exception for those Domain Controllers with the additional integrity=2 setting:

javax.naming.AuthenticationNotSupportedException: [LDAP: error code 8 - 00002028: LdapErr: DSID-0C09023C, comment: The server requires binds to turn on integrity checking if SSL\TLS are not already active on the connection, data 0, v4563

AD supports simple Binds, Kerberos (GSSAPI) and DIGEST-MD5 (SASL). The later two option can be configured to work with Request Signing:

  • you must use the fully qualified hostname the LDAP Server think it has in the URL (you might need to make a /etc/hosts entry if your domains are not completely resolved by DNS.
  • you Must enable DIGEST-MD5 as the Security mechanism
  • you must in addition to this request the auth-int quality of protection (otherwise no signing will be recognized)
  • the Account/principal you want to use for logins on the ActiveDirectory domain must have the reversible password encryption turned on and you must set the password after this change. If the user only has a password hash the LDAP directory will not be able to check the password and reject it (unfortunately with the same error as when the password is wrong):
    javax.naming.AuthenticationException: [LDAP: error code 49 - 8009030C: LdapErr: DSID-0C090569, comment: AcceptSecurityContext error, data 52e, v4563

If you use TLS (ldaps) instead, then you don't need to worry about signing. In fact in this case even simple binds will work. (And just for completeness, if you use SASL then you can't request auth-int or auth-conf in the TLS case with Microsoft Directory Service).

Powershell script to generate SQL Server TLS certificate (self-signed)

I just wanted to dump this script somewhere. It generatess a self-signed TLS certificate which is compatible with SQL Server. The requirements are:

  • Does not use RSA-PSS signature cause JDBC (Java8) clients can't verify them (yet)
  • Allows to set CN and SAN for various server names (defaults to computer name)
  • Uses classic CryptoAPI CSP not a new CNG KSP, since SQL Server / SChannel cant access those keys (would be good because of the extra key isolation)
  • Therefore uses RSA not ECDSA
  • Makes the key not exportable, should be run on SQL Server and machine must be backedup (you can always generate a new certificate so dont accept export risk
# Create Self Signed RSA Cert for SQL Server usage
# 
# Customize:
# + -Subject should contain hostname (or virtal name for FCI)
# + -FriendlyName is anything which helps you to recognize the key
# + -DnsName should list all variants (FQDN) of hostnames used by clients (VIP+Machines)
# + -NotAfter set expire accoring to your policy
# + (Non)Exportable is more secure but harder to manage
#
# - Using RSASSA-PSS (-AlternateSignatureAlgorithm) does not work with Java 8 clients:
#    Caused by: java.security.NoSuchAlgorithmException: 1.2.840.113549.1.1.10 Signature not available
# - Using CNG (Software KSP, Platform KSP) does not work with SQL Server
# - No ECDSA possible since CNG KSP is used
# - sets extended key usage id-kp-serverAuth
New-SelfSignedCertificate -Type SSLServerAuthentication `
    -Subject "CN=$env:COMPUTERNAME" -FriendlyName 'SQL Server RSA2048 G1' `
    -DnsName "$env:COMPUTERNAME",'localhost.' `
    -KeyAlgorithm 'RSA' -KeyLength 2048 -Hash 'SHA256' `
    -TextExtension '2.5.29.37={text}1.3.6.1.5.5.7.3.1' `
    -NotAfter (Get-Date).AddMonths(36) `
    -KeyExportPolicy NonExportable -KeySpec KeyExchange `
    -Provider 'Microsoft RSA SChannel Cryptographic Provider' `
    -CertStoreLocation Cert:\LocalMachine\My `
| fl -Property Thumbprint,FriendlyName,DnsNameList,NotAfter,PrivateKey,SerialNumber,Subject,Issuer

Write-Warning 'You need to open MMC "Manage Machine Certificates", select new cert in "Personal > Certificates"'
Write-Warning 'and specify "All Tasks > Manage private Keys...". Add MSSQL service login (NT Service\MSSQL$INST) with READ.

(Gist)

After generating the certificate manual steps to give service user access is needed (I would love to scrpt this with powershell too, but it seems to require deeper COM magic)

Reset Java Web Start (javaws, jp2launcher, JNLP) file associations

Since I was currently playing around with making JavaWeb Start of IcedTea-Web a bit more robust I was collecting the registry keys where JWS would associate itself with files and URL schemes.

As a result I created a Windows Registry export file which DELETES all those locations, so you have a system reset to the beginning (before you reinstall Java SE or OpenJDK installers). Here is the file, in case you need something similar. Note that it only deals with JNLP related entries, no other settings from JDKs are destroyed.

In case you wonder Firefox will remeber the JNLP entries in the handlers.json file in the profile. I make it a habit to delete that file every now and then.

Download: deljnlp.reg

Windows Registry Editor Version 5.00

; deletes most of the file associations dealing 
; with Java Web Start JNLP files and URLs

; !!! use at your own risk !!! regedit /s deljnlp.reg

[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.jnlp]

[-HKEY_CURRENT_USER\Software\Classes\.jnlp]

[-HKEY_CLASSES_ROOT\.jnlp]


[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\jnlp]

[-HKEY_CURRENT_USER\Software\Classes\jnlp]

[-HKEY_CLASSES_ROOT\jnlp]


[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\jnlps]

[-HKEY_CURRENT_USER\Software\Classes\jnlps]

[-HKEY_CLASSES_ROOT\jnlps]


[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\JNLPFile]

[-HKEY_CURRENT_USER\Software\Classes\JNLPFile]

[-HKEY_CLASSES_ROOT\JNLPFile]


[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\jnlp_auto_file]

[-HKEY_CURRENT_USER\Software\Classes\jnlp_auto_file]

[-HKEY_CLASSES_ROOT\jnlp_auto_file]


[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\MIME\Database\Content Type\application/x-java-jnlp-file]

[-HKEY_CURRENT_USER\Software\Classes\MIME\Database\Content Type\application/x-java-jnlp-file]

[-HKEY_CLASSES_ROOT\MIME\Database\Content Type\application/x-java-jnlp-file]


[-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.jnlp]


; TODO the following list might not be complete, if scripting: "*_.jnlp"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts]
"JNLPFile_.jnlp"=-
"jnlp_auto_file_.jnlp"=-
"Applications\\javaws.exe_.jnlp"=-
"Applications\\javaws.bat_.jnlp"=-
"Applications\\javaws.cmd_.jnlp"=-
"Applications\\jp2launcher_.jnlp"=-
"Applications\\notepad.exe_.jnlp"=-
"Applications\\wordpad.exe_.jnlp"=-

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts]
"JNLPFile_.jnlp"=-
"jnlp_auto_file_.jnlp"=-
"Applications\\javaws.exe_.jnlp"=-
"Applications\\javaws.bat_.jnlp"=-
"Applications\\javaws.cmd_.jnlp"=-
"Applications\\jp2launcher_.jnlp"=-
"Applications\\notepad.exe_.jnlp"=-
"Applications\\wordpad.exe_.jnlp"=-


[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe]

[-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe]


[-HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe]

[-HKEY_CURRENT_USER\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\App Paths\javaws.exe]


[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\JavaWebStart.isInstalled]

[-HKEY_CURRENT_USER\Software\Classes\JavaWebStart.isInstalled]

[-HKEY_LOCAL_MACHINE\SOFTWARE\Classes\JavaWebStart.isInstalled.1.8.0.0]

[-HKEY_CURRENT_USER\Software\Classes\JavaWebStart.isInstalled.1.8.0.0]

Let´s Encrypt (this blog)

Lange hat es gedauert, aber jetzt läuft auf neskaya, dem Server hinter eckenfels.net kein Apache httpd auf port 80. (Nur noch nginx in minimalkonfig um auf https und 443 umzuleiten.)

Der https content wird weiterhin von einem Apache mit mod_ssl und einem Let's Encrypt Zertifikat das von EFF certbot verwaltet wird ausgeliefert.

Es ist eine kleine Unmöglichkeit einen apache 2.4 auf port 80 und 443 gleichzeitig zu konfigurieren...

Aber damit sind jetzt alle Seiten https verschlüsselt (und ggf. müssen noch einige sites daraufhin angepasst werden).

TCP Keepalive bei Oracle Servern

Zu einer TCP Verbindung zwischen zwei Endpunkten gehört auch, sich darüber einig zu sein ob eine Verbindung noch besteht oder nicht. Wartet eine Seite auf weitere Daten und ist die andere Seite inzwischen verschwunden, so sieht die wartende Seite nichts von dem TCP Verbindungsabbruch.

Dies geschieht nur, wenn die TCP Verbindung unsauber abgebrochen wurde. Ursachen einer solchen unsauberen Verbindungsbeendigung könnte Stromausfall, Hardwarecrash, Kernelpanic oder das unerwartete entfernen der IP-Addresse (wie z.B. bei Failover-Clustern) sein. Ebenso kann ein Netzwerkausfall dazu führen dass die Beendigung einer Verbindung nur auf einer Seite bekannt wird. Manche Firewalls die eine Verbindung nur eine bestimmte Zeit zulassen können hier auch zu Problemen führen.

Übrigens, ein Beenden der Anwendung (auch mit kill -9) alleine führt nicht zu so einer Situation. Denn das Betriebsystem schliesst alle Sockets eines beendeten Prozesses sauber.

Welche Methoden stehen für ein Endpunkt zur Verfügung einseitig bestehende Verbindungen mit Lese-Operationen zu erkennen?

  1. sie implementiert einen Lese-Timeout. Dies hat den Vorteil dass auch hängende Gegenstellen erkannt werden. Problem dabei ist aber die Dauer des Timouts. Ist dieser zu kurz gewählt so werden Verbindungen vorzeitig beendet wenn die Gegenstelle langsam antwortet. Um dies zu verhindern muss der Timeout recht groß gewählt werden. Bei Anwendungen die nur sehr sporadisch Anfragen bekommen und deren Verbindungen lange Zeit idle sein sollen lassen sich deswegen Timeouts fast garnicht verwenden.
  2. die Seite die lesend auf dem Socket wartet muss regelmäßig Daten senden oder erwarten. Dies wird gemeinhin als Heartbeat bezeichnet. Dieser hat aber den Nachteil dass zum einen das eingesetzte Protokoll solche Heartbeat Nachrichten erlauben (wenn ich auf eine Antwort warte und stattdessen eine Heartbeat Nachricht erhalte). Die Implementierung wird dadurch komplexer, braucht z.B. mehrere Threads oder Asynchrone methoden. Zudem hat das Versenden von Heartbeat Nachrichten auf einer Verbindung den Nachteil dass im Falle einer Unerreichbarkeit der TCP retry Mechanismus zuschlägt, der ggf. Relativ lange benötigen kann bis er Probleme feststellt,
  3. eine weitaus unproblematischere Möglichkeit ist der TCP Keepalive Mechanismus. Dieser wird entgegen seinem Namen hauptsächlich dazu verwendet abgerissene Connections zu erkennen. Der Mechanismus funktioniert so, dass auf der Seite einer TCP Verbindung auf der Keepalive aktiv ist (ggf. auf beiden) regelmäßig geprüft wird ob eine erfolgreiche Kommunikation stattfand, oder der Socket idle ist. Wenn er Idle ist weil nichts versendet oder empfangen wurde so sendet der Keepalive Mechanismus ein leeres TCP Paket. Die Anwendungen selbst bekommen davon nichts mit, nur der TCP Stack der Gegenseite bestätigt das Paket. in Fehler beim versenden führt zu einem Abbruch der Verbindung, genauso wie das ausbleiben von einer Antwort nach eingestellter Wiederholung.

Um das in der Praxis zu sehen habe ich eine Oracle 12c mit „Dead Client Detection“ konfiguriert. Dabei handelt es sich um eine Option welche den TCP Keepalive auf eingehenden Client Connections anschaltet (und auch gleich auf den angegebenen Wert in Minuten konfiguriert). Das ist deswegen ganz praktisch weil der default Wert unter Linux bei 2h liegt und damit nicht nur relativ nutzlos ist, sondern auch zum testen sehr langatmig.

Wenn ich jetzt eine Oracle Verbindung aufbaue, so werden nur Daten an den Server gesendet wenn ich im Client einen SQL Befehl absetze. Mit netstat oder dem neueren ss (socket statistics) kann man den Zustand der TCP timer einer einzelnen Verbindung betrachten. In diesem Fall verwende ich die -o option gibt die timer Information mit aus.

[oracle@bernd-db centos]$ netstat -tnpo
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0      0 10.14.100.82:22         10.0.103.42:53111       ESTABLISHED -   on (0.22/0/0)
tcp        0      0 127.0.0.1:58466         127.0.0.1:1521          ESTABLISHED 1207/ora_lreg_orcl   off (0.00/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (22.76/0/0)

Um nur die relevante Verbindung fortwährend zu betrachten verwende ich die -c option. Mittels grep beschränke ich mich auf eine Verbindung. (Dieses Filtern kann mit ss effizienter gemacht werden, aber auf meinem Testsystem gibt es keine große Anzahl an Verbindungen) Parallel dazu läuft folgender tcpdump Befehl

[oracle@bernd-db centos]$ sudo tcpdump  -ttt -nn --dont-verify-checksums -U -v host 10.0.103.42 and port 54076 &
[1] 18819
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

[oracle@bernd-db centos]$ netstat -tnpoc 2>/dev/null | grep 10.0.103.42:54076
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (8.66/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (7.65/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (6.64/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.64/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.63/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.62/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.60/0/0)

 00:00:00.000000 IP (tos 0x0, ttl 64, id 2241, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 3621868956, win 1184, length 0
 00:00:00.001712 IP (tos 0x0, ttl 124, id 10289, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [.], ack 1, win 256, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.60/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.59/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.59/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.58/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.57/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (53.93/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (52.92/0/0)
...

Was ist passiert? In diesem Beispiel sieht man die letzten 8 Sekunden des herabzählenden keepalive timers. In der ganzen Minute davor wurden keine Daten ausgetauscht, und so sendet der Kernel beim Ablauf des Timers ein leeres ACK Paket (Flags [.] length 0) und wartet 6 weitere Sekunden bevor er prüft ob dieses angekommen ist. Wenn ja setzt er einen neuen Timer auf (60s abzüglich der bereits gewarteten knapp 6s)

Der Keepalive Timer wird bei Oracle 12.2 mit der Option sqlnet.expire_time=1 in ${ORACLE_HOME}/network/admin/sqlnet.ora auf eine Minute konfiguriert. Das Wiederholungsintervall von 6s wird durch Oracle fest vorgegeben.

Wenn ich jetzt 18 Sekunden bevor der Timer abläuft eine Client Anfrage stelle, so ändert sich die Ausgabe etwas:

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (19.71/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (18.70/0/0)

 00:01:41.833122 IP (tos 0x0, ttl 124, id 10335, offset 0, flags [DF], proto TCP (6), length 61)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 1:22, ack 1, win 256, length 21
 00:00:00.000306 IP (tos 0x0, ttl 64, id 2242, offset 0, flags [DF], proto TCP (6), length 55)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 1:16, ack 22, win 1184, length 15
 00:00:00.007188 IP (tos 0x0, ttl 124, id 10337, offset 0, flags [DF], proto TCP (6), length 53)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 22:35, ack 16, win 256, length 13
 00:00:00.000077 IP (tos 0x0, ttl 64, id 2243, offset 0, flags [DF], proto TCP (6), length 55)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 16:31, ack 35, win 1184, length 15
 00:00:00.041996 IP (tos 0x0, ttl 124, id 10339, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [.], ack 31, win 256, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (17.69/0/0)

 00:00:00.881484 IP (tos 0x0, ttl 124, id 10342, offset 0, flags [DF], proto TCP (6), length 139)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 35:134, ack 31, win 256, length 99
 00:00:00.000610 IP (tos 0x0, ttl 64, id 2244, offset 0, flags [DF], proto TCP (6), length 234)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 31:225, ack 134, win 1184, length 194
 00:00:00.003408 IP (tos 0x0, ttl 124, id 10344, offset 0, flags [DF], proto TCP (6), length 61)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [P.], seq 134:155, ack 225, win 255, length 21
 00:00:00.000238 IP (tos 0x0, ttl 64, id 2245, offset 0, flags [DF], proto TCP (6), length 55)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [P.], seq 225:240, ack 155, win 1184, length 15
 00:00:00.041847 IP (tos 0x0, ttl 124, id 10346, offset 0, flags [DF], proto TCP (6), length 40)
    10.0.103.42.54076 > 10.14.100.82.1521: Flags [.], ack 240, win 255, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (16.69/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (15.68/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (14.67/0/0)
...
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (7.62/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (6.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.61/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.60/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.59/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.58/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.57/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.57/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (42.22/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (41.21/0/0)

Es ist aber wieder zu sehen dass der Timer erst auf 0 zählt, dann erkennt dass ein Austausch vor 18 Sekunden stattfand, und deswegen einen neuen Timer mit der verbleibenden Restzeit von 40 Sekunden aufzieht aber kein ACK Paket versendet.

Um jetzt zu sehen was passiert wenn der Client keine Antworten versendet installiere ich einfach eine Firewall Regel die alle Pakete verwirft (DROP nicht REJECT):

[oracle@bernd-db centos]$ iptables -I INPUT -p tcp -s 10.0.103.42 --sport 54076 -j DROP

Im Folgenden habe ich die Antwort-Pakete (die tcpdump noch sieht) entfernt:

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.31/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.30/0/0)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.30/0/0)

 00:02:00.190237 IP (tos 0x0, ttl 64, id 2258, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 155, win 1184, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.31/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.30/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.29/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.28/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.27/0/1)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.27/0/1)

 00:00:06.014269 IP (tos 0x0, ttl 64, id 2259, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 155, win 1184, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.27/0/2)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.27/0/2)
...
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.04/0/9)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.03/0/9)

 00:00:06.014242 IP (tos 0x0, ttl 64, id 2267, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [.], ack 155, win 1184, length 0

tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (5.04/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (4.04/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (3.03/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (2.02/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (1.01/0/10)
tcp6       0      0 10.14.100.82:1521       10.0.103.42:54076       ESTABLISHED 18470/oracleorcl     keepalive (0.01/0/10)

 00:00:06.013377 IP (tos 0x0, ttl 64, id 2268, offset 0, flags [DF], proto TCP (6), length 40)
    10.14.100.82.1521 > 10.0.103.42.54076: Flags [R.], seq 240, ack 155, win 1184, length 0

Wenn dieses mal der Timer abläuft ohne Datenaustausch, so wird das Keepalive Paket vom Server versendet und der Timer auf 6s gestellt. Da das Paket aber nicht durchkommt, so kam auch nach 6s keine Antwort zurück. Der Kernel auf Serverseite erkennt dies, zählt den Keepalive Zähler hoch (letzte Stelle innerhalb von timer (time/retries/keepalives)), sendet noch ein Paket und wartet erneut 6s.

Oracle hat den Retry auf 10 Probes konfiguriert, entsprechend wird bei der letzten Runde (nach einer Minue) kein Keepalive Paket mehr gesendet sondern die Connection resettet. (Flags [R.] - In meinem Fall bekommt der Client das RST nicht, wegen der Firewall). Hätte ich die Firewall Regel vor Ablauf der 10 retries entfernt, so hätte der normale Keepalive Ablauf wieder weiter gemacht.

Falls die Anwendung keine Konfiguration von TCP Keepalives pro Socket zulässt, so werden folgende Linux Kernel Werte verwendet:

[oracle@bernd-db oracle]$ sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9

Es gibt noch eine Besonderheit, wenn der Keepalive wiederholt wird, aber gleichzeitig auch Daten versendet werden, so wird der Keepalive Mechanismus ausgesetzt, stattdessen versucht der normale Retry Mechanismus Daten zu versenden, aber das soll Gegenstand eines anderen Blogposts sein.

Windows Server 2016 Images mittels DSIM für OpenStack vorbereiten

Für unsere OpenStack DevCloud benötigten wir Images für OpenStack die mit cloudbase-init starten. Dazu gibt es bei Cloudbase ein GIT repository das die nötigen (Powershell) Scripte bereitstellt. https://github.com/cloudbase/windows-openstack-imaging-tools Als ich dies jedoch auf einem Windows 2012 R2 Server nach Anleitung ausführen wollte was das resultierende Image nicht startfähig. Es gab Warnungen dass DSIM nicht aktuell genug ist und anscheinend wurden auch die VirtIO Treiber nicht integriert. Hier sind ein paar Schritte mit denen es dann geklappt hat: a) Git4Windows installieren. b) ein Arbeitsverzeichnis anlegen und die Image Tools dorthin auschecken:
    mkdir c:\work
    cd /d c:\work
    git clone https://github.com/cloudbase/windows-openstack-imaging-tools.git
c) Aus einer Windows Server 2016 Installations-ISO das File sources\install.wim (5,6GB) extrahieren und nach c:\work\source\install.wim legen. (Ich habe dies mit einer Volume License ISO als auch dem 180 Tage Eval ISO erfolgreich gemacht) d) Für Windows Server 2016 muss man eine DSIM Version verwenden die neuer ist. Dazu habe ich das Windows ADK für Windows 10, Version 1703 (Hardware Dev Center) auf dem Server installiert. Es gibt zwar auf der Webseite an dass dies nur für Windows 10 ist, jedoch war es in meinem Fall auch für Windows Server 2016 (Rollup CDs) notwendig. e) nach der Installation ist es Wichtig das DSIM Verzeichnis dieses neuen Kits im Pfad vor dem Windows Systemverzeichnis zu haben (da dort eine alte DSIM Version gefunden wird). Man kann `(Get-Command dism.exe).Path` verwenden um zu sehen welches DISM verwendet wird. Ich habe es mir einfach gemacht und in das verwendete Powershell script einfach einen passenden Pfad gesetzt. f) Im Beispielscript werden die RedHat VirtIO Treiber heruntergeladen, ich hatte die URL auf eine neue Version angepasst. g) Das Offline Beispielscript habe ich angepasst dass es alle Verzeichnisse unter c:\work sucht, dass es nur ein 15GB Image anlegt (und weil dieses noch transportiert und gestartet werden muss habe ich es auch als QCow2 angelegt. Hier das komplette script (auszuführen als privelegierter lokaler admin im Unterverzeichnis example): Continue reading "Windows Server 2016 Images mittels DSIM für OpenStack vorbereiten"

Converting IMAGE to VARBINARY(max) on SQL Server

With Microsoft SQL Server 2005 the IMAGE and (N)TEXT large data types have been deprecated in favor of VARBINARY(MAX) and (N)VARCHAR(max). We have some older database schema at customers who still use the IMAGE type so I am preparing a note to guide the migration.

There is an easy ALTER TABLE command to convert the existing tables, however it is not clear what the impact of this conversion is. So I took a deeper look. I noticed that converting a VARBINARY(max) back to IMAGE caused a lot of I/O and log usage. The other way around this seems to not happen and I can not repeat the problem.

Nevertheless I tried a few scenarios with type and parameter conversion. With IMAGE the default is to not inline data and it is configured with the 'text in row' table option which allows to define a limit of data which is inlined.

The following statement creates 4 tables with different settings for images, two heap tables and two clustered:

DROP TABLE tableHeapWithImageDefault;
DROP TABLE tableHeapWithImageInline;
DROP TABLE tableClusteredWithImageDefault;
DROP TABLE tableClusteredWithImageInline;

CREATE TABLE tableHeapWithImageDefault (cID INT identity(1,1) PRIMARY KEY NOT NULL, cImage IMAGE);
CREATE TABLE tableClusteredWithImageDefault (cID INT identity(1,1) PRIMARY KEY CLUSTERED NOT NULL, cImage IMAGE);

CREATE TABLE tableHeapWithImageInline (cID INT identity(1,1) PRIMARY KEY NOT NULL, cImage IMAGE);
EXEC sp_tableoption 'tableHeapWithImageInline', 'text in row', 'ON';

CREATE TABLE tableClusteredWithImageInline (cID INT identity(1,1) PRIMARY KEY CLUSTERED NOT NULL, cImage IMAGE);
EXEC sp_tableoption 'tableClusteredWithImageInline', 'text in row', 'ON';

If I query the details I see the following:

select name, type_desc, lob_data_space_id, text_in_row_limit, large_value_types_out_of_row from sys.tables where name like 'table%'

name                             type_desc     text_in_row_limit large_value_types_out_of_row
tableClusteredWithImageDefault   USER_TABLE    0                 0
tableClusteredWithImageInline    USER_TABLE    256               0
tableHeapWithImageDefault        USER_TABLE    0                 0
tableHeapWithImageInline         USER_TABLE    256               0

The large_value_type_out_of_row cannot be modified as long as the table does not contain a new large type.

So lets fill the tables with smaller and larger values (total blob size of (36000+180)*50000=1.6GB) for the cImage column and see what space usage the allocation units have:

while @x < 10 BEGIN
  set @x = @x + 1
  select @x
  BEGIN TRANSACTION
  declare @i int = 0;
  while @i < 5000 BEGIN
    INSERT INTO tableHeapWithImageDefault(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),5)) -- 180
    INSERT INTO tableHeapWithImageDefault(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),1000)) -- 36000
    INSERT INTO tableHeapWithImageInline(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),5))
    INSERT INTO tableHeapWithImageInline(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),1000))
    INSERT INTO tableClusteredWithImageDefault(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),5)) -- 180
    INSERT INTO tableClusteredWithImageDefault(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),1000)) -- 36000
    INSERT INTO tableClusteredWithImageInline(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),5))
    INSERT INTO tableClusteredWithImageInline(cImage) VALUES(replicate(cast(newID() as VARCHAR(max)),1000))
    SET @i = @i + 1
  END
  COMMIT TRANSACTION
END

The following query looks at the three possible types of allocation units (especially IN_ROW_DATA for data stored in the table and LOB_DATA for large external data. The third type, ROW_OVERFLOW_DATA, is not used in this example):

select o.name AS tablename, au.type_desc, CEILING(au.used_pages * 8 /1024.0) as usedMB,
       au.data_pages, au.used_pages, au.total_pages, p.partition_number, p.rows,
       au.used_pages * 8 * 1024 / p.rows as [bytes/row]
FROM sys.allocation_units AS au
  JOIN sys.partitions AS p ON au.container_id = p.hobt_id
  JOIN sys.objects AS o ON p.object_id = o.object_id
WHERE o.name like 'table%';

The result shows as expected where the data is stored (keep in mind the average length for the rows includes long and short rows).

tablename                       type_desc       usedMB    data_pages  used_pages   total_pages  rows  bytes/row
tableHeapWithImageDefault       IN_ROW_DATA          5    582         586          587          100000       48
tableHeapWithImageDefault       LOB_DATA          1778    0           227574       227611       100000    18642
tableHeapWithImageInline        IN_ROW_DATA         16    1961        1971         1971         100000      161
tableHeapWithImageInline        LOB_DATA          1758    0           225003       225035       100000    18432
tableClusteredWithImageDefault  IN_ROW_DATA          5    582         586          587          100000       48
tableClusteredWithImageDefault  LOB_DATA          1778    0           227574       227611       100000    18642
tableClusteredWithImageInline   IN_ROW_DATA         16    1961        1971         1971         100000      161
tableClusteredWithImageInline   LOB_DATA          1758    0           225003       225043       100000    18432

So now we can also record the data pages used for the allocation units so we can verify after conversion that the data has actually not been touched (idea):

select o.name, au.allocation_unit_id, au.type_desc, au.total_pages, au.first_iam_page, au.first_page, au.root_page
from sys.system_internals_allocation_units au
  JOIN sys.system_internals_partitions AS p ON au.container_id = p.partition_id
  JOIN sys.objects AS o ON p.object_id = o.object_id
WHERE o.name like 'table%';

With the following result:

name                           allocation_unit_id type_desc    total   first_iam_page first_page     root_page
tableHeapWithImageDefault      72057594197966848  IN_ROW_DATA  587     0x3DB302000100 0x3CB302000100 0x748707000100
tableHeapWithImageDefault      72057594198032384  LOB_DATA     227611  0x3BB302000100 0x38B302000100 0x38B302000100
tableHeapWithImageInline       72057594198097920  IN_ROW_DATA  1971    0xB91507000100 0x2E8B06000100 0x3FDA00000100
tableHeapWithImageInline       72057594198163456  LOB_DATA     225035  0x478707000100 0x418707000100 0x418707000100
tableClusteredWithImageDefault 72057594198228992  IN_ROW_DATA  587     0x4F8707000100 0x4E8707000100 0x768707000100
tableClusteredWithImageDefault 72057594198294528  LOB_DATA     227611  0x4D8707000100 0x4C8707000100 0x4C8707000100
tableClusteredWithImageInline  72057594198360064  IN_ROW_DATA  1971    0x558707000100 0x548707000100 0x4FDA00000100
tableClusteredWithImageInline  72057594198425600  LOB_DATA     225043  0x578707000100 0x568707000100 0x568707000100

So and now finally we can alter the columns:

SET STATISTICS TIME ON
ALTER TABLE tableHeapWithImageDefault ALTER COLUMN cImage VARBINARY(MAX);
ALTER TABLE tableHeapWithImageInline ALTER COLUMN cImage VARBINARY(MAX);
ALTER TABLE tableClusteredWithImageDefault ALTER COLUMN cImage VARBINARY(MAX);
ALTER TABLE tableClusteredWithImageInline ALTER COLUMN cImage VARBINARY(MAX);
SET STATISTICS TIME OFF

And this returns in elapsed time = 0ms with the following new data layout. What is visible at first is that it has generated empty ROW_OVERFLOW_DATA allocation units for all rows (this might indicate that the conversion could differ if the rows are (nearly) full, which is not the case for our narrow tables in the experiment).

name                           allocation_unit_id type_desc          total first_iam_page first_page     root_page
tableHeapWithImageDefault      72057594197966848  IN_ROW_DATA          587 0x3DB302000100 0x3CB302000100 0x748707000100
tableHeapWithImageDefault      72057594198032384  LOB_DATA          227611 0x3BB302000100 0x38B302000100 0x38B302000100
tableHeapWithImageDefault      72057594198491136  ROW_OVERFLOW_DATA      0 0x000000000000 0x000000000000 0x000000000000
tableHeapWithImageInline       72057594198097920  IN_ROW_DATA         1971 0xB91507000100 0x2E8B06000100 0x3FDA00000100
tableHeapWithImageInline       72057594198163456  LOB_DATA          225035 0x478707000100 0x418707000100 0x418707000100
tableHeapWithImageInline       72057594198556672  ROW_OVERFLOW_DATA      0 0x000000000000 0x000000000000 0x000000000000
tableClusteredWithImageDefault 72057594198228992  IN_ROW_DATA          587 0x4F8707000100 0x4E8707000100 0x768707000100
tableClusteredWithImageDefault 72057594198294528  LOB_DATA          227611 0x4D8707000100 0x4C8707000100 0x4C8707000100
tableClusteredWithImageDefault 72057594198622208  ROW_OVERFLOW_DATA      0 0x000000000000 0x000000000000 0x000000000000
tableClusteredWithImageInline  72057594198360064  IN_ROW_DATA         1971 0x558707000100 0x548707000100 0x4FDA00000100
tableClusteredWithImageInline  72057594198425600  LOB_DATA          225043 0x578707000100 0x568707000100 0x568707000100
tableClusteredWithImageInline  72057594198687744  ROW_OVERFLOW_DATA     0  0x000000000000 0x000000000000 0x000000000000

And on the second glance we notice that neither the AU unit ID nor the page addresses has changed for any of the IN_ROW_DATA or LOB_DATA. So this means the change from IMAGE to VARBINARY(max) is low impact for the tested cases.

When turning on the out_of_row setting for the table(Heap/Clustered)WithDefault tables (which had no inline data before) the situation does not change. The elapsed time = 0ms and the result is unchanged:

SET STATISTICS TIME ON
exec sp_tableoption 'tableHeapWithImageDefault', 'large value types out of row', '1';
exec sp_tableoption 'tableClusteredWithImageDefault', 'large value types out of row', '1';
SET STATISTICS TIME OFF

Results in this (removed overflows):

name                           allocation_unit_id type_desc   total_pages first_iam_page first_page     root_page
tableHeapWithImageDefault      72057594197966848  IN_ROW_DATA 587         0x3DB302000100 0x3CB302000100 0x748707000100
tableHeapWithImageDefault      72057594198032384  LOB_DATA    227611      0x3BB302000100 0x38B302000100 0x38B302000100
tableClusteredWithImageDefault 72057594198228992  IN_ROW_DATA 587         0x4F8707000100 0x4E8707000100 0x768707000100
tableClusteredWithImageDefault 72057594198294528  LOB_DATA    227611      0x4D8707000100 0x4C8707000100 0x4C8707000100

Running the same option (elasped 15ms) change on the previously inlined-enabled tables:

SET STATISTICS TIME ON
exec sp_tableoption 'tableClusteredWithImageInline', 'large value types out of row', '1';
exec sp_tableoption 'tableHeapWithImageInline', 'large value types out of row', '1';
SET STATISTICS TIME OFF

With the unchanged pages:

name                          allocation_unit_id type_desc   total_pages first_iam_page first_page     root_page
tableHeapWithImageInline      72057594198097920  IN_ROW_DATA 1971        0xB91507000100 0x2E8B06000100 0x3FDA00000100
tableHeapWithImageInline      72057594198163456  LOB_DATA    225035      0x478707000100 0x418707000100 0x418707000100
tableClusteredWithImageInline 72057594198360064  IN_ROW_DATA 1971        0x558707000100 0x548707000100 0x4FDA00000100
tableClusteredWithImageInline 72057594198425600  LOB_DATA    225043      0x578707000100 0x568707000100 0x568707000100

So even that change has no impact. According to MSDN the reason for this is that the LOBs are only changed when updated. So this is also quite safe.

The same should be true for the other way around, however I have seen cases, where it took much longer with lots of logfile usage. Maybe you have an idea how I can recreate this scenario?

where - Das find des armen Windows Mannes

Ich gebe zu, ich benutze Windows. Sogar relativ gerne. Das liegt vor allem daran, dass ich es satt bin an meinem Desktop herumzuspielen (und keinen Zugang zu Macs habe). Aber als Linux developer kenne ich natürlich auch die Stärke einer Shell und der CLI tools. Deswegen habe ich auch immer ein Cygwin installiert so dass ich tools with find und grep nutzen kann. Ich finde die Windows Powershell sehr interessant und mächtig, bin es aber nicht gewohnt diese zu nutzen.

Deswegen lande ich auch oft in einem klassischen Windows Prompt (und ich nutze ConEmu um das erträglich zu machen). Ein Windows command das man auf jeden Fall kennen sollte ist where: es ist eine Kobination aus find, which und locate und erlaubt die Suche nach Files in Verzeichnissen oder Suchpfaden:


Suche ausführbare Befehle

Ohne zusätzliche Option durchsucht where das aktuelle Verzeichnis und den PATH. Das wird dazu benutzt um Befehle zu finden, dabei sind Suchmuster möglich:

C:\WINDOWS\system32>where java*
C:\Windows\System32\javaws.exe
C:\ProgramData\Oracle\Java\javapath\java.exe
C:\ProgramData\Oracle\Java\javapath\javaw.exe
C:\ProgramData\Oracle\Java\javapath\javaws.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\java-rmi.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\java.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\javac.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\javadoc.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\javah.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\javap.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\javaw.exe

Rekursive Suche

Um Dateien mit einem Suchmuster unterhalb eines Pfades zu suchen verwendet man die /r option. Mit der /t option gibt where dann gleich noch ein paar Datei Details aus:

C:\>where /T /R c:\Windows\System32 *.cmd
    843   30.10.2015   08:17:50  c:\Windows\System32\onlinesetup.cmd
    199   30.10.2015   08:17:43  c:\Windows\System32\winrm.cmd

Scripting Hilfe

Zudem gibt es noch seziellen Such Syntax, so kann ich alle in einer Environment Variable enthaltenen Verzeichnisse durchsuchen:

C:\>where $PATH:java
C:\ProgramData\Oracle\Java\javapath\java.exe
C:\Program Files\Java\java-1.8.0-openjdk-1.8.0.111-1\bin\java.exe

Oder aber in einer Liste von Verzeichnissen suchen:

C:\>where "C:\Windows\system32;C:\Windows\SysWOW64":mode
C:\Windows\System32\mode.com
C:\Windows\SysWOW64\mode.com