验证/识别表示IP地址的字符串版本

4

有没有一种聪明/巧妙的方法来分析一个字符串表示的IP地址是否有效以及识别它的版本,从而能够仅仅通过使用UNIX API就能将其转换为适当的结构体?

我不想使用正则表达式,在这方面不需要添加额外的库。


我的第一种方法是:

in_addr addr;
memset( &addr, 0, sizeof( in_addr ) );
// try to convert from standard numbers-and-dots notation into binary data
if( 0 != inet_aton( sIPAddress.c_str(), &addr ) )
{
    return Socket::enIPv4;      // valid IPv4
}

in6_addr addr6;
memset( &addr6, 0, sizeof( in6_addr ) );
if( inet_pton( AF_INET6, sIPAddress.c_str(), &addr6 ) > 0 )
{
    return Socket::enIPv6;      // valid IPv6
}

return Socket::enUnknown;

这里的问题是,如果我传递像 1 这样的字符串,它会成功转换为 IPv4。例如 11111 这样的字符串也会被转换为 IPv4。根据文档:

inet_aton() 返回非零值表示地址有效,返回零表示地址无效。

很明显,这个函数不仅识别 XXX.XXX.XXX.XXX 格式,还进行了更多内部处理。
当然,我可以编写自己的函数(实际上这会很有趣),通过分析字符串来解决问题,但我想使用已经存在并经过测试的函数。这是否可能?还是我应该编写自己的函数?
4个回答

4
根据手册页面,像"a""a.b""a.b.c"这样的字符串都是inet_aton的有效地址。如果只需要“正常”的点分十进制表示法,对于这些地址也可以使用inet_pton

LOL,这个链接中的信息不在RHEL4/5的man页面中XD 无论如何,inet_pton做了同样的事情。所以,它并没有解决我的问题:\ - Kiril Kirov
在这种情况下,inet_pton 存在一个 bug,它应该只识别有效的点分十进制。 - Some programmer dude
有趣。我正在RHE5U3上进行测试,它并不算太旧。 - Kiril Kirov
1
OMFG :X 这完全是我的错。你关于 inet_pton 是非常正确的。非常感谢,已接受并点赞。 - Kiril Kirov

3
如果您愿意使用Boost库,请查看Boost Asioboost::asio::ip::address类非常擅长解析IPv6和IPv4地址,比尝试编写正则表达式等验证输入更加优秀。它还可以跨平台使用。
#include <boost/asio.hpp>
#include <boost/assert.hpp>

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char* argv[]){

    std::string ipv4("192.168.1.1");
    std::string ipv6("::1");
    std::string notIP("1");

    boost::asio::ip::address ipv4Addr = boost::asio::ip::address::from_string(ipv4);
    BOOST_ASSERT(ipv4Addr.is_v4() == true);

    boost::asio::ip::address ipv6Addr = boost::asio::ip::address::from_string(ipv6);
    BOOST_ASSERT(ipv6Addr.is_v6() == true);

    boost::asio::ip::address badAddr;
    try{
        badAddr = boost::asio::ip::address::from_string(notIP);
    }
    catch(const std::exception& e){
        std::cout << "Bad Address:  " << e.what() << std::endl;
    }   

    return 0;
}

这将打印:

$ g++ -Wall -I/usr/local/boost-1.47.0/include/ -L/usr/local/boost-1.47.0/lib -o ./asioTestIP ./asioTestIP.cpp -lboost_system
$ ./asioTestIP 
Bad Address:  Invalid argument

谢谢,但是我明确只问关于标准UNIX API的问题,因为我不想为了这个而添加对另一个库的依赖,因为我可以自己编写一个函数来实现这个。不管怎样还是谢谢 :) - Kiril Kirov

0

这应该可以工作:

#include <iostream>
#include <string>
#include <vector>
#include <stdlib.h>

void printError(const std::string& errStr) {
    std::cerr << errStr << std::endl;
}

bool checkAddress(const std::string& address) {
    std::vector<std::string> arr;
    int k = 0;
    arr.push_back(std::string());
    for (std::string::const_iterator i = address.begin(); i != address.end(); ++i) {
        if (*i == '.') {
            ++k;
            arr.push_back(std::string());
            if (k == 4) {
                printError("too many '.'");
            }
            continue;
        }
        if (*i >= '0' && *i <= '9') {
            arr[k] += *i;
        } else {
            printError("wrong character");
            return false;
        }
        if (arr[k].size() > 3) {
            printError("size exceeded");
            return false;
        }
    }
    if (k != 3) {
        printError("not enough '.'");
        return false;
    }
    for (int i = 0; i != 4; ++i) {
        const char* nPtr = arr[i].c_str();
        char* endPtr = 0;
        const unsigned long a = ::strtoul(nPtr, &endPtr, 10);
        if (nPtr == endPtr) {
            printError("invalid numeric input");
            return false;
        }
        if (a > 255) {
            printError("out of range");
            return false;
        }
    }
    return true;
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        return -1;
    }
    std::string address = argv[1];
    if (checkAddress(address)) {
        std::cout << "address ok" << std::endl;
    } else {
        std::cout << "bad address" << std::endl;
    }
    return 0;
}

我知道怎么写,这不是我的问题。无论如何,顺便说一下,这将只识别IPv4。IPv6支持几种格式(据我所知为3种),这可能会非常容易出现错误。 - Kiril Kirov

0
我曾在一个项目中解决过这个问题。首先,我制作了IP地址的EBNF,然后创建了算法。它运行得很好,不使用任何外部库。
您需要调用CheckWholeIp方法并将ip字符串传递给它(例如:CheckWholeIp(“127.0.0.1”))。如果符合ipv4方案,则返回int 0,否则返回1。但是需要注意的是,像127.055.008.001这样的地址也会被接受。没有必要过滤前导零。
让我知道您的想法 ;)
cpp文件:
    #include "CheckIp.h"

CheckIp::CheckIp()
{
}

int CheckIp::CheckWholeIp(string s)
{

    int size = s.size();
    int psum = 0;
    //check amount of points
    for (int i = 0; i <= size; i++) {
        if (s[i] == '.')psum++;
    }
    if (psum != 3)return 1;

    //write stringblocks
    string sbl0 = "";
    string sbl1 = "";
    string sbl2 = "";
    string sbl3 = "";
    int ii = 0;

    while (s[ii] != '.') {
        sbl0 = sbl0 + s[ii];
        ii++;
    }
    ii++;
    while (s[ii] != '.') {
        sbl1 = sbl1 + s[ii];
        ii++;
    }
    ii++;
    while (s[ii] != '.') {
        sbl2 = sbl2 + s[ii];
        ii++;
    }
    ii++;
    while (s[ii] != NULL) {
        sbl3 = sbl3 + s[ii];
        ii++;
    }

    //checking the blocks
    if (CheckBlock(sbl0) == 1 | CheckBlock(sbl1) == 1 | CheckBlock(sbl2) == 1 | CheckBlock(sbl3) == 1) return 1;

    return 0;
}

int CheckIp::CheckBlock(string s)
{
    int sizeb = s.size();
    //checking size of block
    if (sizeb > 3) return 1;
    string ss;
    for (int i = 0; i < sizeb; i++) {
        ss = s[i];
        if (CheckNumber(ss) == 1) return 1;
    }

    return 0;
}

int CheckIp::CheckNumber(string s)
{
    if (s == "0" | s == "1" | s == "2" | s == "3" | s == "4" | s == "5" | s == "6" | s == "7" | s == "8" | s == "9") {
        return 0;
    }
    else {
        return 1;
    }

}

标题:

    #pragma once
#include <string>

using namespace std;

ref class CheckIp
{
public:
    CheckIp();

    static int CheckWholeIp(string s);

private: static int CheckBlock(string s);

        static int CheckNumber(string s);

};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接