inet_pton(): Argument #1 ($ip) must not contain any null bytes src/XF/Util/Ip.php:14

M@rc

Well-known member
Affected version
2.3.7
Server Info:
PHP 8.3.29
10.6.24-MariaDB


Tested on my test forum with no addons.

Steps to reproduce:
  1. Enable Writing before registering.
  2. From a logged out account, write a reply and hit Post Reply.
  3. Click the Login button.
register.webp

The screen will return the error:
A server error occurred. Please try again later.

The server error log will have:

Code:
ValueError: inet_pton(): Argument #1 ($ip) must not contain any null bytes src/XF/Util/Ip.php:14
Generated by: Unknown account Jan 16, 2026 at 9:58 PM

Stack trace
#0 src/XF/Util/Ip.php(14): inet_pton([invalid])
#1 src/XF/Entity/Ip.php(25): XF\Util\Ip::stringToBinary([invalid], false)
#2 src/XF/Mvc/Entity/Entity.php(842): XF\Entity\Ip->verifyIp([invalid], 'ip', 6, Array)
#3 src/XF/Mvc/Entity/Entity.php(683): XF\Mvc\Entity\Entity->_verifyValueCustom([invalid], 'ip', 6, Array)
#4 src/XF/Mvc/Entity/Entity.php(618): XF\Mvc\Entity\Entity->set('ip', [invalid])
#5 src/XF/Repository/IpRepository.php(29): XF\Mvc\Entity\Entity->__set('ip', [invalid])
#6 src/XF/Service/Post/PreparerService.php(254): XF\Repository\IpRepository->logIp(1, [invalid], 'post', 97)
#7 src/XF/Service/Post/PreparerService.php(199): XF\Service\Post\PreparerService->writeIpLog([invalid])
#8 src/XF/Service/Thread/ReplierService.php(226): XF\Service\Post\PreparerService->afterInsert()
#9 src/XF/Service/ValidateAndSavableTrait.php(42): XF\Service\Thread\ReplierService->_save()
#10 src/XF/PreRegAction/Thread/Reply.php(47): XF\Service\Thread\ReplierService->save()
#11 src/XF/PreRegAction/AbstractHandler.php(132): XF\PreRegAction\Thread\Reply->executeAction(Object(XF\Entity\PreRegAction), Object(XFES\XF\Entity\Thread), Object(XF\Entity\User))
#12 src/XF.php(906): XF\PreRegAction\AbstractHandler->XF\PreRegAction\{closure}()
#13 src/XF/PreRegAction/AbstractHandler.php(125): XF::asVisitor(Object(XF\Entity\User), Object(Closure))
#14 src/XF/Repository/PreRegActionRepository.php(96): XF\PreRegAction\AbstractHandler->completeAction(Object(XF\Entity\PreRegAction), Object(XF\Entity\User))
#15 src/XF/Repository/PreRegActionRepository.php(126): XF\Repository\PreRegActionRepository->completeUserAction(Object(XF\Entity\User), NULL)
#16 src/XF/ControllerPlugin/LoginPlugin.php(198): XF\Repository\PreRegActionRepository->completeUserActionIfPossible(Object(XF\Entity\User), NULL)
#17 src/XF/Pub/Controller/LoginController.php(164): XF\ControllerPlugin\LoginPlugin->completeLogin(Object(XF\Entity\User), true)
#18 src/XF/Mvc/Dispatcher.php(362): XF\Pub\Controller\LoginController->actionLogin(Object(XF\Mvc\ParameterBag))
#19 src/XF/Mvc/Dispatcher.php(264): XF\Mvc\Dispatcher->dispatchClass('XF:Login', 'Login', Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\LoginController), NULL)
#20 src/XF/Mvc/Dispatcher.php(121): XF\Mvc\Dispatcher->dispatchFromMatch(Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\LoginController), NULL)
#21 src/XF/Mvc/Dispatcher.php(63): XF\Mvc\Dispatcher->dispatchLoop(Object(XF\Mvc\RouteMatch))
#22 src/XF/App.php(2824): XF\Mvc\Dispatcher->run()
#23 src/XF.php(806): XF\App->run()
#24 index.php(23): XF::runApp('XF\\Pub\\App')
#25 {main}

Request state
array(4) {
  ["url"] => string(16) "/dev/login/login"
  ["referrer"] => string(35) "https://domain.com/dev/register/"
  ["_GET"] => array(1) {
    ["/dev/login/login"] => string(0) ""
  }
  ["_POST"] => array(5) {
    ["_xfToken"] => string(8) "********"
    ["login"] => string(4) "Marc"
    ["password"] => string(8) "********"
    ["remember"] => string(1) "1"
    ["_xfRedirect"] => string(57) "https://domain.com/dev/threads/this-is-a-test.1/page-3"
  }
}
 
Can confirm.

This also happens with NF's Tickets add-on when trying to convert from ticket to thread and from thread to ticket. According to him (@Naz), this is an XF issue.
Code:
ValueError: inet_pton(): Argument #1 ($ip) must not contain any null bytes in src\XF\Util\Ip.php at line 14
inet_pton() in src\XF\Util\Ip.php at line 14
XF\Util\Ip::stringToBinary() in src\XF\Entity\Ip.php at line 25
XF\Entity\Ip->verifyIp() in src\XF\Mvc\Entity\Entity.php at line 842
XF\Mvc\Entity\Entity->_verifyValueCustom() in src\XF\Mvc\Entity\Entity.php at line 683
XF\Mvc\Entity\Entity->set() in src\XF\Mvc\Entity\Entity.php at line 618
XF\Mvc\Entity\Entity->__set() in src\XF\Repository\IpRepository.php at line 29
XF\Repository\IpRepository->logIp() in src\addons\SV\UserEssentials\XF\Repository\Ip.php at line 18
SV\UserEssentials\XF\Repository\Ip->logIp() in src\addons\NF\Tickets\Service\Ticket\Converter\ToThread.php at line 141
NF\Tickets\Service\Ticket\Converter\ToThread->copyIp() in src\addons\NF\Tickets\Service\Ticket\Converter\ToThread.php at line 71
NF\Tickets\Service\Ticket\Converter\ToThread->_save() in src\XF\Service\ValidateAndSavableTrait.php at line 42
XF\Service\Thread\CreatorService->save() in src\addons\NF\Tickets\Pub\Controller\Ticket.php at line 1990
NF\Tickets\Pub\Controller\Ticket->actionConvertToThread() in src\XF\Mvc\Dispatcher.php at line 362
XF\Mvc\Dispatcher->dispatchClass() in src\XF\Mvc\Dispatcher.php at line 264
XF\Mvc\Dispatcher->dispatchFromMatch() in src\XF\Mvc\Dispatcher.php at line 121
XF\Mvc\Dispatcher->dispatchLoop() in src\XF\Mvc\Dispatcher.php at line 63
XF\Mvc\Dispatcher->run() in src\XF\App.php at line 2824
XF\App->run() in src\XF.php at line 814
XF::runApp() in index.php at line 23
 
I'm getting this:

Steps to reproduce:
  1. Enable Writing before registering.
  2. From a logged out account, write a reply and hit Post Reply.
  3. Click the Login button.
After attempting to login and getting therror page, if you hit then browser back button and then attempt the login again it seems fine.
 
I did a lot of testing with addons enabled/disabled and it was quite frustrating as the issue seemed random.

  • At first I'd thought it was due to an addon because with all addons disabled it didn't happen.
  • Then, re-enabling vBulletinRedirection addon it happened
  • But then, after disabling vBulletin imports (ie back to all addons disabled) it was happening again
(NB: I had cleared cache and cookies between tests)

scratch-head.png


Here is the error with no addons enabled.

Code:
    ValueError: inet_pton(): Argument #1 ($ip) must not contain any null bytes src/XF/Util/Ip.php:14

    Generated by: Unknown account Feb 5, 2026 at 11:20 AM

Stack trace

#0 src/XF/Util/Ip.php(14): inet_pton([invalid])
#1 src/XF/Entity/Ip.php(25): XF\Util\Ip::stringToBinary([invalid], false)
#2 src/XF/Mvc/Entity/Entity.php(842): XF\Entity\Ip->verifyIp([invalid], 'ip', 6, Array)
#3 src/XF/Mvc/Entity/Entity.php(683): XF\Mvc\Entity\Entity->_verifyValueCustom([invalid], 'ip', 6, Array)
#4 src/XF/Mvc/Entity/Entity.php(618): XF\Mvc\Entity\Entity->set('ip', [invalid])
#5 src/XF/Repository/IpRepository.php(29): XF\Mvc\Entity\Entity->__set('ip', [invalid])
#6 src/XF/Service/Post/PreparerService.php(254): XF\Repository\IpRepository->logIp(2363, [invalid], 'post', 620846)
#7 src/XF/Service/Post/PreparerService.php(199): XF\Service\Post\PreparerService->writeIpLog([invalid])
#8 src/XF/Service/Thread/ReplierService.php(236): XF\Service\Post\PreparerService->afterInsert()
#9 src/XF/Service/ValidateAndSavableTrait.php(42): XF\Service\Thread\ReplierService->_save()
#10 src/XF/PreRegAction/Thread/Reply.php(47): XF\Service\Thread\ReplierService->save()
#11 src/XF/PreRegAction/AbstractHandler.php(132): XF\PreRegAction\Thread\Reply->executeAction(Object(XF\Entity\PreRegAction), Object(XF\Entity\Thread), Object(XF\Entity\User))
#12 src/XF.php(914): XF\PreRegAction\AbstractHandler->XF\PreRegAction\{closure}()
#13 src/XF/PreRegAction/AbstractHandler.php(125): XF::asVisitor(Object(XF\Entity\User), Object(Closure))
#14 src/XF/Repository/PreRegActionRepository.php(96): XF\PreRegAction\AbstractHandler->completeAction(Object(XF\Entity\PreRegAction), Object(XF\Entity\User))
#15 src/XF/Repository/PreRegActionRepository.php(126): XF\Repository\PreRegActionRepository->completeUserAction(Object(XF\Entity\User), NULL)
#16 src/XF/ControllerPlugin/LoginPlugin.php(199): XF\Repository\PreRegActionRepository->completeUserActionIfPossible(Object(XF\Entity\User), NULL)
#17 src/XF/Pub/Controller/LoginController.php(183): XF\ControllerPlugin\LoginPlugin->completeLogin(Object(XF\Entity\User), true)
#18 src/XF/Mvc/Dispatcher.php(362): XF\Pub\Controller\LoginController->actionLogin(Object(XF\Mvc\ParameterBag))
#19 src/XF/Mvc/Dispatcher.php(264): XF\Mvc\Dispatcher->dispatchClass('XF:Login', 'Login', Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\LoginController), NULL)
#20 src/XF/Mvc/Dispatcher.php(121): XF\Mvc\Dispatcher->dispatchFromMatch(Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\LoginController), NULL)
#21 src/XF/Mvc/Dispatcher.php(63): XF\Mvc\Dispatcher->dispatchLoop(Object(XF\Mvc\RouteMatch))
#22 src/XF/App.php(2824): XF\Mvc\Dispatcher->run()
#23 src/XF.php(814): XF\App->run()
#24 index.php(23): XF::runApp('XF\\Pub\\App')
#25 {main}

Request state

array(4) {
  ["url"] => string(12) "/login/login"
  ["referrer"] => string(39) "https://xf2.cafesaxophone.com/register/"
  ["_GET"] => array(0) {
  }
  ["_POST"] => array(5) {
    ["_xfToken"] => string(8) "********"
    ["login"] => string(6) "Curtis"
    ["password"] => string(8) "********"
    ["remember"] => string(1) "1"
    ["_xfRedirect"] => string(91) "https://xf2.cafesaxophone.com/threads/cork-for-mouthpieces-no-alternatives-yet.37553/page-7"
  }
}
 
My gut feeling is that this is to do with Cloudflare. After pausing Cloudflare the issue seems to not be there, with or without addons. Testing isn't straightforward because (I think) it it takes some some for pausing CF to take effect. maybe someone can confirm that I'm right or wrong about this?
 
XenForo uses inet_pton() to determine if an IP address is binary or not. You can no longer do that reliably starting with the following versions of PHP:
  • 8.5.1
  • 8.4.16
  • 8.3.29
  • 8.2.30
  • 8.1.34
I'm doing something similar in my Cloudflare addon (for the geotargeting part), and had to change it to this for the new version of PHP:

PHP:
        if (empty($value))
        {
            return null;
        }

        if (strpos($value, "\0") !== false)
        {
            $binaryValue = $value;
        }
        else
        {
            $binaryValue = @inet_pton($value);
            if ($binaryValue === false)
            {
                $binaryValue = $value;
            }
        }

...basically make sure the IP you are checking isn't empty and also doesn't contain a null character before you fall back to inet_pton() to check it.

If it contains a null character (which would happen if a binary version of an IP contained a 0... for example 11.22.0.44), you know it's a binary representation already and don't need to rely on inet_pton() to make that determination (which you can't do anymore with the new versions of PHP). It's sporadic because it only throws an error if the binary IP being checked has a 0 in it.
 
Changed stringToBinary in src\XF\Util\Ip.php to:

PHP:
    public static function stringToBinary(string $ip, bool $throw = true)
    {
        // PHP 8.3.29+: inet_pton() throws ValueError if the input contains null bytes.
        // If the caller passed already-binary IP bytes (4 for v4, 16 for v6), accept it.
        if (strpos($ip, "\0") !== false)
        {
            if (strlen($ip) === 4 || strlen($ip) === 16)
            {
                return $ip;
            }
  
            if ($throw)
            {
                throw new \InvalidArgumentException('Invalid string IP: ' . $ip);
            }
  
            return false;
        }

        try
        {
            $output = @inet_pton($ip);
        }
        catch (\ValueError $e)
        {
            // For safety on newer PHP versions, treat it as invalid input
            $output = false;
        }
  
        if ($output === false && $throw)
        {
            throw new \InvalidArgumentException('Invalid string IP: ' . $ip);
        }
  
        return $output;
    }

This "fixes" it until the XF team resolves it officially. Not much of a PHP coder myself, so take it with a grain of salt lol.

Also you'll get file mismatch errors from the daily health check with that change, which can be ignored.

To suppress those errors, you can remove the following line from src/addons/XF/hashes.json:
Code:
"src/XF/Util/Ip.php": "a54d560861dfcf5ff33fb1eeefde73eae7a300733e270a9702ba338613b8dda7",
 
Changed stringToBinary in src\XF\Util\Ip.php to:

PHP:
    public static function stringToBinary(string $ip, bool $throw = true)
    {
        // PHP 8.3.29+: inet_pton() throws ValueError if the input contains null bytes.
        // If the caller passed already-binary IP bytes (4 for v4, 16 for v6), accept it.
        if (strpos($ip, "\0") !== false)
        {
            if (strlen($ip) === 4 || strlen($ip) === 16)
            {
                return $ip;
            }
  
            if ($throw)
            {
                throw new \InvalidArgumentException('Invalid string IP: ' . $ip);
            }
  
            return false;
        }

        try
        {
            $output = @inet_pton($ip);
        }
        catch (\ValueError $e)
        {
            // For safety on newer PHP versions, treat it as invalid input
            $output = false;
        }
  
        if ($output === false && $throw)
        {
            throw new \InvalidArgumentException('Invalid string IP: ' . $ip);
        }
  
        return $output;
    }

This "fixes" it until the XF team resolves it officially. Not much of a PHP coder myself, so take it with a grain of salt lol.

Also you'll get file mismatch errors from the daily health check with that change, which can be ignored.
It's not going to be fixed by PHP devs. They made the change intentionally to deal with a potential security issue related to null characters within strings on a few functions. inet_pton() was never intended to be a check to see if something is binary or not (even though you could use it that way).
 
Changed stringToBinary in src\XF\Util\Ip.php to:

PHP:
    public static function stringToBinary(string $ip, bool $throw = true)
    {
        // PHP 8.3.29+: inet_pton() throws ValueError if the input contains null bytes.
        // If the caller passed already-binary IP bytes (4 for v4, 16 for v6), accept it.
        if (strpos($ip, "\0") !== false)
        {
            if (strlen($ip) === 4 || strlen($ip) === 16)
            {
                return $ip;
            }
 
            if ($throw)
            {
                throw new \InvalidArgumentException('Invalid string IP: ' . $ip);
            }
 
            return false;
        }

        try
        {
            $output = @inet_pton($ip);
        }
        catch (\ValueError $e)
        {
            // For safety on newer PHP versions, treat it as invalid input
            $output = false;
        }
 
        if ($output === false && $throw)
        {
            throw new \InvalidArgumentException('Invalid string IP: ' . $ip);
        }
 
        return $output;
    }

This "fixes" it until the XF team resolves it officially. Not much of a PHP coder myself, so take it with a grain of salt lol.

Also you'll get file mismatch errors from the daily health check with that change, which can be ignored.

To suppress those errors, you can remove the following line from src/addons/XF/hashes.json:
Code:
"src/XF/Util/Ip.php": "a54d560861dfcf5ff33fb1eeefde73eae7a300733e270a9702ba338613b8dda7",

Confirmed! A XF to XF import no longer fails, if the IP address of a post does not contain a zero, e. g. 203.0.113.2 or 192.168.0.2. I just did another test import and the issue no longer not occurs. Good catch! :)
 
But why then does it happen with Cloudflare active, but not happen when paused?
Not sure... but it's sporadic. Will only happen when it's used on an IP address with a 0 octet... for example 22.33.0.44. How XenForo uses PHP to process IPs normally wouldn't be affected by Cloudflare unless you're doing something weird like using Cloudflare to alter the IP address that PHP sees. Like maybe you are using Cloudflare to anonymize IP addresses into 11.22.0.0 or something... Either way, if the IP (as PHP sees it) has a 0 octet, it's a problem. If not, there's no issue. With or without Cloudflare (or anything else).
 
Back
Top Bottom