博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
STUN用于判断NATType的过程及方法
阅读量:4167 次
发布时间:2019-05-26

本文共 43884 字,大约阅读时间需要 146 分钟。

stund用于判断NatType的接口的用法

首先来看stund中用于判断NatType的接口的用法。这里主要来看stund中的STUN客户端client.cxx的实现。client.cxx是一个常规的C/C++ app,这个app的主要code如下:

void usage() {    cerr << "Usage:" << endl         << "    ./client stunServerHostname [testNumber] [-v] [-p srcPort] "                 "[-i nicAddr1] [-i nicAddr2] [-i nicAddr3] " << endl         << "For example, if the STUN server was larry.gloo.net, you could do:" << endl         << "    ./client larry.gloo.net" << endl         << "The testNumber is just used for special tests." << endl         << " test 1 runs test 1 from the RFC. For example:" << endl         << "    ./client larry.gloo.net 0" << endl << endl << endl;}#define MAX_NIC 3StunAddress4 stunServerAddr;int main(int argc, char* argv[]) {    assert( sizeof(UInt8 ) == 1);    assert( sizeof(UInt16) == 2);    assert( sizeof(UInt32) == 4);    initNetwork();    cout << "STUN client version " << STUN_VERSION << endl;    int testNum = 0;    bool verbose = false;    stunServerAddr.addr = 0;    int srcPort = 0;    StunAddress4 sAddr[MAX_NIC];    int retval[MAX_NIC];    int numNic = 0;    for (int i = 0; i < MAX_NIC; i++) {        sAddr[i].addr = 0;        sAddr[i].port = 0;        retval[i] = 0;    }    for (int arg = 1; arg < argc; arg++) {        if (!strcmp(argv[arg], "-v")) {            verbose = true;        } else if (!strcmp(argv[arg], "-i")) {            arg++;            if (argc <= arg) {                usage();                exit(-1);            }            if (numNic >= MAX_NIC) {                cerr << "Can not have more than " << MAX_NIC <<" -i options" << endl;                usage();                exit(-1);            }            stunParseServerName(argv[arg], sAddr[numNic++]);        } else if (!strcmp(argv[arg], "-p")) {            arg++;            if (argc <= arg) {                usage();                exit(-1);            }            srcPort = strtol(argv[arg], NULL, 10);        } else {            char* ptr;            int t = strtol(argv[arg], &ptr, 10);            if (*ptr == 0) {                // conversion worked                testNum = t;                cout << "running test number " << testNum << endl;            } else {                bool ret = stunParseServerName(argv[arg], stunServerAddr);                if (ret != true) {                    cerr << argv[arg] << " is not a valid host name " << endl;                    usage();                    exit(-1);                }            }        }    }    if (srcPort == 0) {        srcPort = stunRandomPort();    }    if (numNic == 0) {        // use default        numNic = 1;    }    for (int nic = 0; nic < numNic; nic++) {        sAddr[nic].port = srcPort;        if (stunServerAddr.addr == 0) {            usage();            exit(-1);        }        if (testNum == 0) {            bool presPort = false;            bool hairpin = false;            NatType stype = stunNatType(stunServerAddr, verbose, &presPort, &hairpin, srcPort, &sAddr[nic]);            if (nic == 0) {                cout << "Primary: ";            } else {                cout << "Secondary: ";            }            switch (stype) {                case StunTypeFailure:                    cout << "Some stun error detetecting NAT type";                    retval[nic] = -1;                    exit(-1);                    break;                case StunTypeUnknown:                    cout << "Some unknown type error detetecting NAT type";                    retval[nic] = 0xEE;                    break;                case StunTypeOpen:                    cout << "Open";                    retval[nic] = 0x00;                    break;                case StunTypeIndependentFilter:                    cout << "Independent Mapping, Independent Filter";                    if (presPort)                        cout << ", preserves ports";                    else                        cout << ", random port";                    if (hairpin)                        cout << ", will hairpin";                    else                        cout << ", no hairpin";                    retval[nic] = 0x02;                    break;                case StunTypeDependentFilter:                    cout << "Independent Mapping, Address Dependent Filter";                    if (presPort)                        cout << ", preserves ports";                    else                        cout << ", random port";                    if (hairpin)                        cout << ", will hairpin";                    else                        cout << ", no hairpin";                    retval[nic] = 0x04;                    break;                case StunTypePortDependedFilter:                    cout << "Independent Mapping, Port Dependent Filter";                    if (presPort)                        cout << ", preserves ports";                    else                        cout << ", random port";                    if (hairpin)                        cout << ", will hairpin";                    else                        cout << ", no hairpin";                    retval[nic] = 0x06;                    break;                case StunTypeDependentMapping:                    cout << "Dependent Mapping";                    if (presPort)                        cout << ", preserves ports";                    else                        cout << ", random port";                    if (hairpin)                        cout << ", will hairpin";                    else                        cout << ", no hairpin";                    retval[nic] = 0x08;                    break;                case StunTypeFirewall:                    cout << "Firewall";                    retval[nic] = 0x0A;                    break;                case StunTypeBlocked:                    cout << "Blocked or could not reach STUN server";                    retval[nic] = 0x0C;                    break;                default:                    cout << stype;                    cout << "Unkown NAT type";                    retval[nic] = 0x0E;  // Unknown NAT type                    break;            }            cout << "\t";            cout.flush();            if (!hairpin) {                retval[nic] |= 0x10;            }            if (presPort) {                retval[nic] |= 0x01;            }        } else if (testNum == 100) {

可以看到这个app主要做了3件事情:

  1. 解析参数。主要从参数中获得STUN server的地址,及本地用于发送数据包所用的UDP端口号。

  2. 调用stunNatType()函数判断NatType。判断NatType的全部逻辑都在这个函数里。

  3. 将stunNatType()函数返回的NatType进行格式化并打印输出,以便于人的阅读。

接着来看stunNatType()函数的实现

stunNatType()函数的实现

stunNatType()函数的实现如下:

NatType stunNatType(StunAddress4& dest, bool verbose, bool* preservePort,  // if set, is return for if NAT preservers ports or not                    bool* hairpin,  // if set, is the return for if NAT will hairpin packets                    int port,  // port to use for the test, 0 to choose random port                    StunAddress4* sAddr  // NIC to use                    ) {    assert( dest.addr != 0);    assert( dest.port != 0);    if (hairpin) {        *hairpin = false;    }    if (port == 0) {        port = stunRandomPort();    }    UInt32 interfaceIp = 0;    if (sAddr) {        interfaceIp = sAddr->addr;    }    Socket myFd1 = openPort(port, interfaceIp, verbose);    Socket myFd2 = openPort(port + 1, interfaceIp, verbose);    if ((myFd1 == INVALID_SOCKET) || (myFd2 == INVALID_SOCKET)) {        cerr << "Some problem opening port/interface to send on" << endl;        return StunTypeFailure;    }    assert( myFd1 != INVALID_SOCKET);    assert( myFd2 != INVALID_SOCKET);    bool respTestI = false;    bool isNat = true;    StunAddress4 testImappedAddr;    bool respTestI2 = false;    bool mappedIpSame = true;    StunAddress4 testI2mappedAddr;    StunAddress4 testI2dest = dest;    bool respTestII = false;    bool respTestIII = false;    bool respTestHairpin = false;    bool respTestPreservePort = false;    memset(&testImappedAddr, 0, sizeof(testImappedAddr));    StunAtrString username;    StunAtrString password;    username.sizeValue = 0;    password.sizeValue = 0;#ifdef USE_TLS     stunGetUserNameAndPassword( dest, username, password );#endif    int count = 0;    while (count < 7) {        struct timeval tv;        fd_set fdSet;#ifdef WIN32        unsigned int fdSetSize;#else        int fdSetSize;#endif        FD_ZERO(&fdSet);        fdSetSize = 0;        FD_SET(myFd1, &fdSet);        fdSetSize = (myFd1 + 1 > fdSetSize) ? myFd1 + 1 : fdSetSize;        FD_SET(myFd2, &fdSet);        fdSetSize = (myFd2 + 1 > fdSetSize) ? myFd2 + 1 : fdSetSize;        tv.tv_sec = 0;        tv.tv_usec = 150 * 1000;  // 150 ms        if (count == 0)            tv.tv_usec = 0;        int err = select(fdSetSize, &fdSet, NULL, NULL, &tv);        int e = getErrno();        if (err == SOCKET_ERROR) {            // error occured            cerr << "Error " << e << " " << strerror(e) << " in select" << endl;            return StunTypeFailure;        } else if (err == 0) {            // timeout occured            count++;            if (!respTestI) {                stunSendTest(myFd1, dest, username, password, 1, verbose);            }            if ((!respTestI2) && respTestI) {                // check the address to send to if valid                if ((testI2dest.addr != 0) && (testI2dest.port != 0)) {                    stunSendTest(myFd1, testI2dest, username, password, 10, verbose);                }            }            if (!respTestII) {                stunSendTest(myFd2, dest, username, password, 2, verbose);            }            if (!respTestIII) {                stunSendTest(myFd2, dest, username, password, 3, verbose);            }            if (respTestI && (!respTestHairpin)) {                if ((testImappedAddr.addr != 0) && (testImappedAddr.port != 0)) {                    stunSendTest(myFd1, testImappedAddr, username, password, 11, verbose);                }            }        } else {            //if (verbose) clog << "-----------------------------------------" << endl;            assert( err>0);            // data is avialbe on some fd            for (int i = 0; i < 2; i++) {                Socket myFd;                if (i == 0) {                    myFd = myFd1;                } else {                    myFd = myFd2;                }                if (myFd != INVALID_SOCKET) {                    if (FD_ISSET(myFd,&fdSet)) {                        char msg[STUN_MAX_MESSAGE_SIZE];                        int msgLen = sizeof(msg);                        StunAddress4 from;                        getMessage(myFd, msg, &msgLen, &from.addr, &from.port, verbose);                        StunMessage resp;                        memset(&resp, 0, sizeof(StunMessage));                        stunParseMessage(msg, msgLen, resp, verbose);                        if (verbose) {                            clog << "Received message of type " << resp.msgHdr.msgType << "  id="                                 << (int) (resp.msgHdr.id.octet[0]) << endl;                        }                        switch (resp.msgHdr.id.octet[0]) {                            case 1: {                                if (!respTestI) {                                    testImappedAddr.addr = resp.mappedAddress.ipv4.addr;                                    testImappedAddr.port = resp.mappedAddress.ipv4.port;                                    respTestPreservePort = (testImappedAddr.port == port);                                    if (preservePort) {                                        *preservePort = respTestPreservePort;                                    }                                    testI2dest.addr = resp.changedAddress.ipv4.addr;                                    if (sAddr) {                                        sAddr->port = testImappedAddr.port;                                        sAddr->addr = testImappedAddr.addr;                                    }                                    count = 0;                                }                                respTestI = true;                            }                                break;                            case 2: {                                respTestII = true;                            }                                break;                            case 3: {                                respTestIII = true;                            }                                break;                            case 10: {                                if (!respTestI2) {                                    testI2mappedAddr.addr = resp.mappedAddress.ipv4.addr;                                    testI2mappedAddr.port = resp.mappedAddress.ipv4.port;                                    mappedIpSame = false;                                    if ((testI2mappedAddr.addr == testImappedAddr.addr)                                            && (testI2mappedAddr.port == testImappedAddr.port)) {                                        mappedIpSame = true;                                    }                                }                                respTestI2 = true;                            }                                break;                            case 11: {                                if (hairpin) {                                    *hairpin = true;                                }                                respTestHairpin = true;                            }                                break;                        }                    }                }            }        }    }    // see if we can bind to this address    //cerr << "try binding to " << testImappedAddr << endl;    Socket s = openPort(0/*use ephemeral*/, testImappedAddr.addr, false);    if (s != INVALID_SOCKET) {        closesocket(s);        isNat = false;        //cerr << "binding worked" << endl;    } else {        isNat = true;        //cerr << "binding failed" << endl;    }    if (verbose) {        clog << "test I = " << respTestI << endl;        clog << "test II = " << respTestII << endl;        clog << "test III = " << respTestIII << endl;        clog << "test I(2) = " << respTestI2 << endl;        clog << "is nat  = " << isNat << endl;        clog << "mapped IP same = " << mappedIpSame << endl;        clog << "hairpin = " << respTestHairpin << endl;        clog << "preserver port = " << respTestPreservePort << endl;    }#if 0    // implement logic flow chart from draft RFC    if (respTestI) {        if (isNat) {            if (respTestII) {                return StunTypeConeNat;            } else {                if (mappedIpSame) {                    if (respTestIII) {                        return StunTypeRestrictedNat;                    } else {                        return StunTypePortRestrictedNat;                    }                } else {                    return StunTypeSymNat;                }            }        } else {            if (respTestII) {                return StunTypeOpen;            } else {                return StunTypeSymFirewall;            }        }    } else {        return StunTypeBlocked;    }#else    if (respTestI) {  // not blocked        if (isNat) {            if (mappedIpSame) {                if (respTestII) {                    return StunTypeIndependentFilter;                } else {                    if (respTestIII) {                        return StunTypeDependentFilter;                    } else {                        return StunTypePortDependedFilter;                    }                }            } else {  // mappedIp is not same                return StunTypeDependentMapping;            }        } else {  // isNat is false            if (respTestII) {                return StunTypeOpen;            } else {                return StunTypeFirewall;            }        }    } else {        return StunTypeBlocked;    }#endif    return StunTypeUnknown;}

可以看到这个函数主要做了几件事:

  1. 打开了两个UDP socket。后续会通过这两个socket来进行数据包的发送,并最终根据这些数据包的响应数据包的情况来判断NatType。

  2. 向STUN server发送请求。调用stunSendTest()函数发送了5种不同类型的消息,各个消息之间的差异也仅仅在与stunSendTest()函数的testNum参数不同。这里我们也用testNum来区分不同的消息,我们称它们分别为类型1,类型2,类型3,类型10及类型11的消息。

    其中类型10和类型11的消息依赖于类型1的消息的响应,但类型2和类型3的消息的发送则与类型1的消息的发送及响应相互独立,因而它们可以与类型1的消息并行的发送。

  3. 接收发送的消息的响应。

    从类型1的消息的响应中获得的东西比较多。类型10和类型11的消息要发送的目标地址,都来源于类型1的消息的响应。
    类型10的消息发向类型1的消息的响应的changedAddress地址。这个地址是STUN server的副IP地址及端口号。
    类型11的消息则发向类型1的消息的响应的testImappedAddr地址,这个地址是发送消息的地址的出口公网地址,向这个消息发送消息实际是向本节点在发送消息,这么做的实际目的是为了测试节点所连接的NAT是否支持消息的回传,或者说测试NAT是否是hairpin的。即如果这个类型11的消息通过NAT并最终被发送给本节点且本节点接收到了这个消息,则说明本节点所连接的NAT是hairpin的。
    STUN终端会从类型10的消息的响应中获得相同的本地网络地址到另外的网络地址(IP地址与类型1的目标IP地址不同)的出口公网地址,并用这个地址与类型1的响应中携带的那个出口公网地址进行比较,以此来判断当前节点所连接的NAT是否是对称型的。
    除了类型1和类型10之外,发送其它的消息主要就是看看是否能获得对应的响应。

  4. 根据发送的这5种不同类型的消息的响应来判断当前节点所连接的NAT的类型并返回给调用者。

下面我们再用几张图来详细地说明,这些消息都发到了哪里,而响应又是从哪里返回回来的。

先说明一下,stund的STUN Server需要部署在一台具有双网卡且每个网卡都有一个自己公网IP地址的主机上。STUN Server的两个IP可以称为IPAddr1(primary IP)和IPAddr2(alt IP),两个端口可以称为Port1(primary port)和Port2(alt port),这两个端口默认分别为3478和3479。STUN Server会打开4个sockets,每个IP两个分别对应两个不同的端口。

首先是消息1:

160644_Zta3_919237.png

消息1从客户端的第一个端口Port1发向STUN Server的IPAddr1:Port1,响应中则会携带客户端发送消息的端口的出口网络地址,及IPAddr2:Port2,以为后续发送消息10及消息11做准备。

消息2:

161232_AfFx_919237.png

消息2从客户端的第二个端口,发向STUN Server的IPAddr1:Port1,这个消息请求STUN Server将响应从它的IPAddr2:Port1发送回来,也就是相对于接收数据包的网络地址而言切换一下IP地址的网络地址。

发送这个消息的目的是什么呢?这个消息的响应如果能接收到的话,说明当前节点连接的NAT的类型为全锥型的,说明NAT对于发向其内部的主机的数据包几乎没有限制。

这里为什么要从第二个端口发送消息呢?这主要是因为,类型10的消息会发向IPAddr2:Port1,这实际上会对消息2的响应的接收产生干扰。如果一个地址向IPAddr2:Port1发送了消息,即使当前节点连接的NAT的类型不是全锥型的,从IPAddr2:Port1发回来的消息也可能被接收到。

消息3:

161329_M7lo_919237.png

消息3同样从客户端的第二个端口发出,且同样发向STUN Server的IPAddr1:Port1,但这个消息请求STUN Server将响应从它的IPAddr1:Port2发送回来,也就是相对于接收数据包的网络地址而言切换一下端口的网络地址。

在消息2的响应接收不到的情况下,如果消息3的响应可以接收到,说明NAT对传入给内部主机的包是限制IP而不限制端口的,也就是说当前节点连接的NAT的类型是IP限制型的。

消息4:

161413_TSdS_919237.png

针对多主机部署的STUN Server优化

由上面的过程,不难看到,STUN Server的部署有一个比较大的限制,即要求部署的主机具有双网卡,这对于我们当前遍地云主机的环境而言,部署起来是不那么方便的。主要是对于类型2的消息,客户端请求STUN Server切换一下IP地址将消息发回来。

因而一种用于stund的STUN Server的优化设计应运而生,结构如下图:

162407_B2RO_919237.png

这种设计主要是让STUN Server只绑定一个IP上的两个端口,同时在STUN之间建立一个通信信道,以便于类型2的消息能得到合适的处理。

针对多主机部署的STUN Server的优化当前实现的状况:

Github主页:

STUN消息的格式

具体可多主机部署的STUN Server要如何设计?这还要从STUN消息的具体格式说起。接着来看下STUN消息的具体格式。

首先是客户端发送的请求的格式。我们可以通过stunSendTest()函数的实现来对这个问题做一番了解:

static void stunSendTest(Socket myFd, StunAddress4& dest, const StunAtrString& username, const StunAtrString& password,                         int testNum, bool verbose) {    assert( dest.addr != 0);    assert( dest.port != 0);    bool changePort = false;    bool changeIP = false;    bool discard = false;    switch (testNum) {        case 1:        case 10:        case 11:            break;        case 2:            //changePort=true;            changeIP = true;            break;        case 3:            changePort = true;            break;        case 4:            changeIP = true;            break;        case 5:            discard = true;            break;        default:            cerr << "Test " << testNum << " is unkown\n";            assert(0);    }    StunMessage req;    memset(&req, 0, sizeof(StunMessage));    stunBuildReqSimple(&req, username, changePort, changeIP, testNum);    char buf[STUN_MAX_MESSAGE_SIZE];    int len = STUN_MAX_MESSAGE_SIZE;    len = stunEncodeMessage(req, buf, len, password, verbose);    if (verbose) {        clog << "About to send msg of len " << len << " to " << dest << endl;    }    sendMessage(myFd, buf, len, dest.addr, dest.port, verbose);    // add some delay so the packets don't get sent too quickly#ifdef WIN32 // !cj! TODO - should fix this up in windows    clock_t now = clock();    assert( CLOCKS_PER_SEC == 1000 );    while ( clock() <= now+10 ) {};#else    usleep(10 * 1000);#endif}

从这里似乎也得不到太多STUN消息格式的具体信息,细节都被放在stunBuildReqSimple()和stunEncodeMessage()两个函数中了,接着来看这两个函数的实现:

static char*encodeAtrChangeRequest(char* ptr, const StunAtrChangeRequest& atr) {    ptr = encode16(ptr, ChangeRequest);    ptr = encode16(ptr, 4);    ptr = encode32(ptr, atr.value);    return ptr;}. . . . . .unsigned int stunEncodeMessage(const StunMessage& msg, char* buf, unsigned int bufLen, const StunAtrString& password,                               bool verbose) {    assert(bufLen >= sizeof(StunMsgHdr));    char* ptr = buf;    ptr = encode16(ptr, msg.msgHdr.msgType);    char* lengthp = ptr;    ptr = encode16(ptr, 0);    ptr = encode(ptr, reinterpret_cast
(msg.msgHdr.id.octet), sizeof(msg.msgHdr.id)); if (verbose) clog << "Encoding stun message: " << endl; if (msg.hasMappedAddress) { if (verbose) clog << "Encoding MappedAddress: " << msg.mappedAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, MappedAddress, msg.mappedAddress); } if (msg.hasResponseAddress) { if (verbose) clog << "Encoding ResponseAddress: " << msg.responseAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, ResponseAddress, msg.responseAddress); } if (msg.hasChangeRequest) { if (verbose) clog << "Encoding ChangeRequest: " << msg.changeRequest.value << endl; ptr = encodeAtrChangeRequest(ptr, msg.changeRequest); } if (msg.hasSourceAddress) { if (verbose) clog << "Encoding SourceAddress: " << msg.sourceAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, SourceAddress, msg.sourceAddress); } if (msg.hasChangedAddress) { if (verbose) clog << "Encoding ChangedAddress: " << msg.changedAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, ChangedAddress, msg.changedAddress); } if (msg.hasUsername) { if (verbose) clog << "Encoding Username: " << msg.username.value << endl; ptr = encodeAtrString(ptr, Username, msg.username); } if (msg.hasPassword) { if (verbose) clog << "Encoding Password: " << msg.password.value << endl; ptr = encodeAtrString(ptr, Password, msg.password); } if (msg.hasErrorCode) { if (verbose) clog << "Encoding ErrorCode: class=" << int(msg.errorCode.errorClass) << " number=" << int(msg.errorCode.number) << " reason=" << msg.errorCode.reason << endl; ptr = encodeAtrError(ptr, msg.errorCode); } if (msg.hasUnknownAttributes) { if (verbose) clog << "Encoding UnknownAttribute: ???" << endl; ptr = encodeAtrUnknown(ptr, msg.unknownAttributes); } if (msg.hasReflectedFrom) { if (verbose) clog << "Encoding ReflectedFrom: " << msg.reflectedFrom.ipv4 << endl; ptr = encodeAtrAddress4(ptr, ReflectedFrom, msg.reflectedFrom); } if (msg.hasXorMappedAddress) { if (verbose) clog << "Encoding XorMappedAddress: " << msg.xorMappedAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, XorMappedAddress, msg.xorMappedAddress); } if (msg.xorOnly) { if (verbose) clog << "Encoding xorOnly: " << endl; ptr = encodeXorOnly(ptr); } if (msg.hasServerName) { if (verbose) clog << "Encoding ServerName: " << msg.serverName.value << endl; ptr = encodeAtrString(ptr, ServerName, msg.serverName); } if (msg.hasSecondaryAddress) { if (verbose) clog << "Encoding SecondaryAddress: " << msg.secondaryAddress.ipv4 << endl; ptr = encodeAtrAddress4(ptr, SecondaryAddress, msg.secondaryAddress); } if (password.sizeValue > 0) { if (verbose) clog << "HMAC with password: " << password.value << endl; StunAtrIntegrity integrity; computeHmac(integrity.hash, buf, int(ptr - buf), password.value, password.sizeValue); ptr = encodeAtrIntegrity(ptr, integrity); } if (verbose) clog << endl; encode16(lengthp, UInt16(ptr - buf - sizeof(StunMsgHdr))); return int(ptr - buf);}void stunBuildReqSimple(StunMessage* msg, const StunAtrString& username, bool changePort, bool changeIp, unsigned int id) { assert( msg); memset(msg, 0, sizeof(*msg)); msg->msgHdr.msgType = BindRequestMsg; for (int i = 0; i < 16; i = i + 4) { assert(i+3<16); int r = stunRand(); msg->msgHdr.id.octet[i + 0] = r >> 0; msg->msgHdr.id.octet[i + 1] = r >> 8; msg->msgHdr.id.octet[i + 2] = r >> 16; msg->msgHdr.id.octet[i + 3] = r >> 24; } if (id != 0) { msg->msgHdr.id.octet[0] = id; } msg->hasChangeRequest = true; msg->changeRequest.value = (changeIp ? ChangeIpFlag : 0) | (changePort ? ChangePortFlag : 0); if (username.sizeValue > 0) { msg->hasUsername = true; msg->username = username; }}

由这些函数的实现,当不难理出来STUN请求消息的格式大体为:

170658_ZxCq_919237.png

整体来看,STUN请求消息分为两个部分,一部分是Header,另一部分是Attr的List。

而Header又包含消息的类型,消息不包含Header的长度,及一个128位16字节的id。在stund中,id的首个字节保存了消息的类型。STUN Server会原封不动的将客户端发过去的消息的id包含在响应中发回给客户端,在stund中,使用了id的首个字节用以区分发出去的不同类型的消息的响应。

Attr的List则是一系列的Attr。Attr的结构大体为,先是一个16位的AttrType,然后是16位的Attr值长度,接着便是Attr的值,而Attr的值所占字节数因Attr的不同而不同。对于判断NatType这个case而言,AttrList中只有一个Attr,及类型为ChangeRequest的Attr,它有一个32位4字节的值。这个Attr用于告诉STUN Server,响应应该从哪个网络地址发回来。

看完了STUN请求消息的格式之后,接着再来看STUN响应消息的格式。这个我们可以从stunServerProcessMsg()函数的实现来了解:

bool stunServerProcessMsg(char* buf, unsigned int bufLen, StunAddress4& from, StunAddress4& secondary,                          StunAddress4& myAddr, StunAddress4& altAddr, StunMessage* resp, StunAddress4* destination,                          StunAtrString* hmacPassword, bool* changePort, bool* changeIp, bool verbose) {    // set up information for default response    memset(resp, 0, sizeof(*resp));    *changeIp = false;    *changePort = false;    StunMessage req;    bool ok = stunParseMessage(buf, bufLen, req, verbose);    if (!ok) {      // Complete garbage, drop it on the floor        if (verbose)            clog << "Request did not parse" << endl;        return false;    }    if (verbose)        clog << "Request parsed ok" << endl;    StunAddress4 mapped = req.mappedAddress.ipv4;    StunAddress4 respondTo = req.responseAddress.ipv4;    UInt32 flags = req.changeRequest.value;    switch (req.msgHdr.msgType) {        case SharedSecretRequestMsg:            if (verbose)                clog << "Received SharedSecretRequestMsg on udp. send error 433." << endl;            // !cj! - should fix so you know if this came over TLS or UDP            stunCreateSharedSecretResponse(req, from, *resp);            //stunCreateSharedSecretErrorResponse(*resp, 4, 33, "this request must be over TLS");            return true;        case BindRequestMsg:            if (!req.hasMessageIntegrity) {                if (verbose)                    clog << "BindRequest does not contain MessageIntegrity" << endl;                if (0) {  // !jf! mustAuthenticate                    if (verbose)                        clog << "Received BindRequest with no MessageIntegrity. Sending 401." << endl;                    stunCreateErrorResponse(*resp, 4, 1, "Missing MessageIntegrity");                    return true;                }            } else {                if (!req.hasUsername) {                    if (verbose)                        clog << "No UserName. Send 432." << endl;                    stunCreateErrorResponse(*resp, 4, 32, "No UserName and contains MessageIntegrity");                    return true;                } else {                    if (verbose)                        clog << "Validating username: " << req.username.value << endl;                    // !jf! could retrieve associated password from provisioning here                    if (strcmp(req.username.value, "test") == 0) {                        if (0) {                            // !jf! if the credentials are stale                            stunCreateErrorResponse(*resp, 4, 30, "Stale credentials on BindRequest");                            return true;                        } else {                            if (verbose)                                clog << "Validating MessageIntegrity" << endl;                            // need access to shared secret                            unsigned char hmac[20];#ifndef NOSSL                            unsigned int hmacSize=20;                            HMAC(EVP_sha1(),                                    "1234", 4,                                    reinterpret_cast
(buf), bufLen-20-4, hmac, &hmacSize); assert(hmacSize == 20);#endif if (memcmp(buf, hmac, 20) != 0) { if (verbose) clog << "MessageIntegrity is bad. Sending " << endl; stunCreateErrorResponse(*resp, 4, 3, "Unknown username. Try test with password 1234"); return true; } // need to compute this later after message is filled in resp->hasMessageIntegrity = true; assert(req.hasUsername); resp->hasUsername = true; resp->username = req.username; // copy username in } } else { if (verbose) clog << "Invalid username: " << req.username.value << "Send 430." << endl; } } } // TODO !jf! should check for unknown attributes here and send 420 listing the // unknown attributes. if (respondTo.port == 0) respondTo = from; if (mapped.port == 0) mapped = from; *changeIp = (flags & ChangeIpFlag) ? true : false; *changePort = (flags & ChangePortFlag) ? true : false; if (verbose) { clog << "Request is valid:" << endl; clog << "\t flags=" << flags << endl; clog << "\t changeIp=" << *changeIp << endl; clog << "\t changePort=" << *changePort << endl; clog << "\t from = " << from << endl; clog << "\t respond to = " << respondTo << endl; clog << "\t mapped = " << mapped << endl; } // form the outgoing message resp->msgHdr.msgType = BindResponseMsg; for (int i = 0; i < 16; i++) { resp->msgHdr.id.octet[i] = req.msgHdr.id.octet[i]; } if (req.xorOnly == false) { resp->hasMappedAddress = true; resp->mappedAddress.ipv4.port = mapped.port; resp->mappedAddress.ipv4.addr = mapped.addr; } if (1) { // do xorMapped address or not resp->hasXorMappedAddress = true; UInt16 id16 = req.msgHdr.id.octet[0] << 8 | req.msgHdr.id.octet[1]; UInt32 id32 = req.msgHdr.id.octet[0] << 24 | req.msgHdr.id.octet[1] << 16 | req.msgHdr.id.octet[2] << 8 | req.msgHdr.id.octet[3]; resp->xorMappedAddress.ipv4.port = mapped.port ^ id16; resp->xorMappedAddress.ipv4.addr = mapped.addr ^ id32; } resp->hasSourceAddress = true; resp->sourceAddress.ipv4.port = (*changePort) ? altAddr.port : myAddr.port; resp->sourceAddress.ipv4.addr = (*changeIp) ? altAddr.addr : myAddr.addr; resp->hasChangedAddress = true; resp->changedAddress.ipv4.port = altAddr.port; resp->changedAddress.ipv4.addr = altAddr.addr; if (secondary.port != 0) { resp->hasSecondaryAddress = true; resp->secondaryAddress.ipv4.port = secondary.port; resp->secondaryAddress.ipv4.addr = secondary.addr; } if (req.hasUsername && req.username.sizeValue > 0) { // copy username in resp->hasUsername = true; assert( req.username.sizeValue % 4 == 0); assert( req.username.sizeValue < STUN_MAX_STRING); memcpy(resp->username.value, req.username.value, req.username.sizeValue); resp->username.sizeValue = req.username.sizeValue; } if (1) { // add ServerName resp->hasServerName = true; const char serverName[] = "Vovida.org " STUN_VERSION; // must pad to mult of 4 assert( sizeof(serverName) < STUN_MAX_STRING); //cerr << "sizeof serverName is " << sizeof(serverName) << endl; assert( sizeof(serverName)%4 == 0); memcpy(resp->serverName.value, serverName, sizeof(serverName)); resp->serverName.sizeValue = sizeof(serverName); } if (req.hasMessageIntegrity & req.hasUsername) { // this creates the password that will be used in the HMAC when then // messages is sent stunCreatePassword(req.username, hmacPassword); } if (req.hasUsername && (req.username.sizeValue > 64)) { UInt32 source; assert( sizeof(int) == sizeof(UInt32)); sscanf(req.username.value, "%x", &source); resp->hasReflectedFrom = true; resp->reflectedFrom.ipv4.port = 0; resp->reflectedFrom.ipv4.addr = source; } destination->port = respondTo.port; destination->addr = respondTo.addr; return true; default: if (verbose) clog << "Unknown or unsupported request " << endl; return false; } assert(0); return false;}

由这个函数的实现,我们不难看出STUN Server发回给客户端的响应的消息格式与请求的格式大体一样,但消息的具体内容有一些区别。消息的格式大体为:

174120_pPyU_919237.png

这个消息里的内容要多一点。

了解了STUN客户端和STUN Server间交互的这些UDP数据包的格式之后,我们就可以确定可双主机部署的STUN Server间通信的消息的格式了。

仔细来看stunServerProcessMsg(),我们注意到,STUN server响应发送的目标地址,以及返回给客户端的它的出口公网地址也就是mappedAddress也没有限定只能是from地址,这些值也可以来源于请求消息。

借助于stund的这些良好设计,可以大大简化我们的可双主机部署的STUN server的设计与实现。STUN server间的消息格式可以为:

182922_6Bzw_919237.png

也就是说,当STUN Server收到类型2的消息时,构造一个格式如上图的消息,并将该消息转发给另为一个STUN Server。其中MappedAddress和ResponseAddress Attr的值都是消息的from地址,即客户端发送消息的端口的出口公网地址。

经过对stunServerProcessMsg()的一番改造,终于可以实现STUN Server的多主机部署,其改造后的实现为:

bool stunServerProcessMsg(StunServerInfo& info, char* buf, unsigned int bufLen, StunAddress4& from,                          StunAddress4& secondary, StunAddress4& myAddr, StunAddress4& altAddr, StunMessage* resp,                          StunAddress4* destination, StunAtrString* hmacPassword, bool* changePort, bool* changeIp,                          bool verbose) {    // set up information for default response    memset(resp, 0, sizeof(*resp));    *changeIp = false;    *changePort = false;    StunMessage req;    bool ok = stunParseMessage(buf, bufLen, req, verbose);    if (!ok) {      // Complete garbage, drop it on the floor        if (verbose)            clog << "Request did not parse" << endl;        return false;    }    if (verbose)        clog << "Request parsed ok" << endl;    StunAddress4 mapped = req.mappedAddress.ipv4;    StunAddress4 respondTo = req.responseAddress.ipv4;    UInt32 flags = req.changeRequest.value;    switch (req.msgHdr.msgType) {        case SharedSecretRequestMsg:            if (verbose)                clog << "Received SharedSecretRequestMsg on udp. send error 433." << endl;            // !cj! - should fix so you know if this came over TLS or UDP            stunCreateSharedSecretResponse(req, from, *resp);            //stunCreateSharedSecretErrorResponse(*resp, 4, 33, "this request must be over TLS");            return true;        case BindRequestMsg:            if (!req.hasMessageIntegrity) {                if (verbose)                    clog << "BindRequest does not contain MessageIntegrity" << endl;                if (0) {  // !jf! mustAuthenticate                    if (verbose)                        clog << "Received BindRequest with no MessageIntegrity. Sending 401." << endl;                    stunCreateErrorResponse(*resp, 4, 1, "Missing MessageIntegrity");                    return true;                }            } else {                if (!req.hasUsername) {                    if (verbose)                        clog << "No UserName. Send 432." << endl;                    stunCreateErrorResponse(*resp, 4, 32, "No UserName and contains MessageIntegrity");                    return true;                } else {                    if (verbose)                        clog << "Validating username: " << req.username.value << endl;                    // !jf! could retrieve associated password from provisioning here                    if (strcmp(req.username.value, "test") == 0) {                        if (0) {                            // !jf! if the credentials are stale                            stunCreateErrorResponse(*resp, 4, 30, "Stale credentials on BindRequest");                            return true;                        } else {                            if (verbose)                                clog << "Validating MessageIntegrity" << endl;                            // need access to shared secret                            unsigned char hmac[20];#ifndef NOSSL                            unsigned int hmacSize=20;                            HMAC(EVP_sha1(),                                    "1234", 4,                                    reinterpret_cast
(buf), bufLen-20-4, hmac, &hmacSize); assert(hmacSize == 20);#endif if (memcmp(buf, hmac, 20) != 0) { if (verbose) clog << "MessageIntegrity is bad. Sending " << endl; stunCreateErrorResponse(*resp, 4, 3, "Unknown username. Try test with password 1234"); return true; } // need to compute this later after message is filled in resp->hasMessageIntegrity = true; assert(req.hasUsername); resp->hasUsername = true; resp->username = req.username; // copy username in } } else { if (verbose) clog << "Invalid username: " << req.username.value << "Send 430." << endl; } } } // TODO !jf! should check for unknown attributes here and send 420 listing the // unknown attributes. if (respondTo.port == 0) respondTo = from; if (mapped.port == 0) mapped = from; *changeIp = (flags & ChangeIpFlag) ? true : false; *changePort = (flags & ChangePortFlag) ? true : false; if (verbose) { clog << "Request is valid:" << endl; clog << "\t flags=" << flags << endl; clog << "\t changeIp=" << *changeIp << endl; clog << "\t changePort=" << *changePort << endl; clog << "\t from = " << from << endl; clog << "\t respond to = " << respondTo << endl; clog << "\t mapped = " << mapped << endl; } // form the outgoing message for (int i = 0; i < 16; i++) { resp->msgHdr.id.octet[i] = req.msgHdr.id.octet[i]; } if (*changeIp && info.altIpFd == INVALID_SOCKET) { resp->msgHdr.msgType = req.msgHdr.msgType; *changeIp = false; *changePort = false; resp->hasChangeRequest = true; resp->changeRequest.value = changePort ? ChangePortFlag : 0; resp->hasMappedAddress = true; resp->mappedAddress.ipv4.port = mapped.port; resp->mappedAddress.ipv4.addr = mapped.addr; resp->hasResponseAddress = true; resp->responseAddress.ipv4.port = from.port; resp->responseAddress.ipv4.addr = from.addr; respondTo.port = info.myAddr.port; respondTo.addr = info.altAddr.addr; if (verbose) { clog << "\t respondTo change = " << respondTo << endl; } } else { resp->msgHdr.msgType = BindResponseMsg; if (req.xorOnly == false) { resp->hasMappedAddress = true; resp->mappedAddress.ipv4.port = mapped.port; resp->mappedAddress.ipv4.addr = mapped.addr; } if (1) { // do xorMapped address or not resp->hasXorMappedAddress = true; UInt16 id16 = req.msgHdr.id.octet[0] << 8 | req.msgHdr.id.octet[1]; UInt32 id32 = req.msgHdr.id.octet[0] << 24 | req.msgHdr.id.octet[1] << 16 | req.msgHdr.id.octet[2] << 8 | req.msgHdr.id.octet[3]; resp->xorMappedAddress.ipv4.port = mapped.port ^ id16; resp->xorMappedAddress.ipv4.addr = mapped.addr ^ id32; } resp->hasSourceAddress = true; resp->sourceAddress.ipv4.port = (*changePort) ? altAddr.port : myAddr.port; resp->sourceAddress.ipv4.addr = (*changeIp) ? altAddr.addr : myAddr.addr; resp->hasChangedAddress = true; resp->changedAddress.ipv4.port = altAddr.port; resp->changedAddress.ipv4.addr = altAddr.addr; if (secondary.port != 0) { resp->hasSecondaryAddress = true; resp->secondaryAddress.ipv4.port = secondary.port; resp->secondaryAddress.ipv4.addr = secondary.addr; } if (req.hasUsername && req.username.sizeValue > 0) { // copy username in resp->hasUsername = true; assert( req.username.sizeValue % 4 == 0); assert( req.username.sizeValue < STUN_MAX_STRING); memcpy(resp->username.value, req.username.value, req.username.sizeValue); resp->username.sizeValue = req.username.sizeValue; } if (1) { // add ServerName resp->hasServerName = true; const char serverName[] = "Vovida.org " STUN_VERSION; // must pad to mult of 4 assert( sizeof(serverName) < STUN_MAX_STRING); //cerr << "sizeof serverName is " << sizeof(serverName) << endl; assert( sizeof(serverName)%4 == 0); memcpy(resp->serverName.value, serverName, sizeof(serverName)); resp->serverName.sizeValue = sizeof(serverName); } if (req.hasMessageIntegrity & req.hasUsername) { // this creates the password that will be used in the HMAC when then // messages is sent stunCreatePassword(req.username, hmacPassword); } if (req.hasUsername && (req.username.sizeValue > 64)) { UInt32 source; assert( sizeof(int) == sizeof(UInt32)); sscanf(req.username.value, "%x", &source); resp->hasReflectedFrom = true; resp->reflectedFrom.ipv4.port = 0; resp->reflectedFrom.ipv4.addr = source; } } destination->port = respondTo.port; destination->addr = respondTo.addr; return true; default: if (verbose) clog << "Unknown or unsupported request " << endl; return false; } assert(0); return false;}

主要的改动即是在发现客户端请求改变IP地址发回响应时,构造如上图中的消息,并发给另一个STUN Server。从而,对于消息2,数据包的流转过程大体如下:

作者:hanpfei
链接:https://www.jianshu.com/p/96e53c00f7ab

转载地址:http://zcqxi.baihongyu.com/

你可能感兴趣的文章
Java多线程(9) - 多线程 - 线程池详解与使用示例
查看>>
Java多线程(10) - 多线程 - CountDownLatch、CyclicBarrier、Semaphore使用示例详解
查看>>
Java多线程(11) - 多线程 - 锁详解:重入锁、公平锁、非公平锁、读写锁、不可重入锁、自旋锁、独享锁、共享锁、互斥锁、悲观锁、乐观锁、分段锁、偏向锁、轻量级锁、重量级锁、CAS算法原理
查看>>
Java网络编程(10) - Netty网络编程常见问题与疑问
查看>>
设置Django连接到Google Cloud SQL(MYSQL)
查看>>
爬虫: 基于Node.js的强大爬虫,能直接发布抓取的文章哦
查看>>
Django学习笔记 扩展User模型
查看>>
Django资料总结
查看>>
Android进阶系列-发布项目到Jcenter
查看>>
基于Zxing的二维码扫描解析库——ZxingPlus
查看>>
算法入门-程序设计入门
查看>>
java数据结构-数据结构的概述
查看>>
java -Math常用方法
查看>>
Android进阶系列-手写数据库框架
查看>>
算法入门-循环结构程序设计
查看>>
算法入门-数组和字符串
查看>>
Android进阶系列-手写高并发网络访问框架
查看>>
Java基础之线程安全基本数据类型
查看>>
Android进阶系列-手写高并发图片加载框架
查看>>
Android基础系列-大纲汇总
查看>>