Lấy địa chỉ IP của máy


94

Câu hỏi này gần giống với câu hỏi trước Lấy địa chỉ IP của máy tính cục bộ -Câu hỏi. Tuy nhiên, tôi cần tìm (các) địa chỉ IP của Máy Linux .

Vì vậy: Làm cách nào để - lập trình trong C ++ - phát hiện địa chỉ IP của máy chủ linux mà ứng dụng của tôi đang chạy. Máy chủ sẽ có ít nhất hai địa chỉ IP và tôi cần một địa chỉ cụ thể (địa chỉ trong một mạng nhất định (địa chỉ công cộng)).

Tôi chắc rằng có một chức năng đơn giản để làm điều đó - nhưng ở đâu?


Để làm cho mọi thứ rõ ràng hơn một chút:

  • Máy chủ rõ ràng sẽ có "localhost": 127.0.0.1
  • Máy chủ sẽ có địa chỉ IP nội bộ (quản lý): 172.16.xx
  • Máy chủ sẽ có địa chỉ IP bên ngoài (công khai): 80.190.xx

Tôi cần tìm địa chỉ IP bên ngoài để liên kết ứng dụng của mình với nó. Rõ ràng là tôi cũng có thể liên kết với INADDR_ANY (và thực sự đó là những gì tôi làm vào lúc này). Tuy nhiên, tôi muốn phát hiện địa chỉ công cộng hơn.


3
Tại sao lại đánh dấu cửa sổ bật lên của ifconfig? Đó thực sự là điều đúng đắn cần làm. Các thư viện thay đổi, nhưng ifconfig và popen sẽ luôn ở đó.
Erik Aronesty

3
Không, ifconfig, routevv không được tán thành cho các iplệnh. Bây giờ bạn nên sử dụng nó để thay thế.
Anders

ifconfig ngày nay vẫn hoạt động trên nhiều kiến ​​trúc hơn bất kỳ cách tiếp cận nào khác. Vì vậy, hãy thay đổi nó thành lệnh ip.when / nếu thích hợp. popen vẫn là giải pháp tốt hơn.
Erik Aronesty

Câu trả lời:


104

Tôi thấy giải pháp ioctl có vấn đề trên os x (tương thích với POSIX nên tương tự với linux). Tuy nhiên getifaddress () sẽ cho phép bạn làm điều tương tự một cách dễ dàng, nó hoạt động tốt đối với tôi trên hệ điều hành x 10.5 và sẽ giống như bên dưới.

Tôi đã làm một ví dụ nhanh dưới đây sẽ in tất cả địa chỉ IPv4 của máy, (bạn cũng nên kiểm tra getifaddrs đã thành công tức là trả về 0).

Tôi đã cập nhật nó cũng hiển thị địa chỉ IPv6.

#include <stdio.h>      
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h> 
#include <string.h> 
#include <arpa/inet.h>

int main (int argc, const char * argv[]) {
    struct ifaddrs * ifAddrStruct=NULL;
    struct ifaddrs * ifa=NULL;
    void * tmpAddrPtr=NULL;

    getifaddrs(&ifAddrStruct);

    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr) {
            continue;
        }
        if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
            // is a valid IP4 Address
            tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addressBuffer[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
            printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
        } else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
            // is a valid IP6 Address
            tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
            char addressBuffer[INET6_ADDRSTRLEN];
            inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
            printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
        } 
    }
    if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct);
    return 0;
}

7
+1 và cảm ơn bạn đã mang đến cách tiếp cận tốt đẹp này thay vì những ioctl nhàm chán! Tuy nhiên, việc xử lý của bạn của IP6 vẫn chạy sai - bạn nên đúc để sockaddr_in6, tức là một cái gì đó giống nhưtmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
Andrey

2
@Andrey, cảm ơn. Tôi đã cập nhật câu trả lời của tôi (đã không có một cơ hội để kiểm tra nó mặc dù)
Twelve47

2
cho lowlan0giao diện trong trường hợp của tôi. Những gì tôi yêu cầu là wlan0hoặc eth0có kết nối internet. Vì vậy, tôi có nên sử dụng strcmphay cách nào tốt hơn?
Sudip Bhattarai

28
  1. Tạo một ổ cắm.
  2. Biểu diễn ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

Đọc /usr/include/linux/if.hđể biết thông tin về cấu trúc ifconfifreqcấu trúc. Điều này sẽ cung cấp cho bạn địa chỉ IP của từng giao diện trên hệ thống. Cũng đọc /usr/include/linux/sockios.hđể biết thêm ioctls.


Tôi có ấn tượng rằng nó sẽ chỉ cung cấp cho bạn địa chỉ của giao diện mà nó liên kết với.
Matt Joiner

@MattJoiner Không, hãy xem trang người đàn ông, SIOCGIFCONF trả về thông tin trên tất cả các giao diện. Một phần của trang người được trích dẫn ở đây: "Trả lại danh sách địa chỉ giao diện (lớp truyền tải). Điều này hiện chỉ có nghĩa là các địa chỉ thuộc họ AF_INET (IPv4) để tương thích. Người dùng chuyển cấu trúc ifconf làm đối số cho ioctl. Nó chứa một . con trỏ đến một mảng của cấu trúc ifreq trong ifc_req và chiều dài của nó bằng byte trong ifc_len kernel lấp đầy ifreqs với tất cả các địa chỉ giao diện L3 hiện đang chạy"
Stéphane

25

Tôi thích câu trả lời của jjvainio . Như Zan Lnyx nói, nó sử dụng bảng định tuyến cục bộ để tìm địa chỉ IP của giao diện ethernet sẽ được sử dụng cho kết nối với một máy chủ bên ngoài cụ thể. Bằng cách sử dụng ổ cắm UDP được kết nối, bạn có thể lấy thông tin mà không thực sự gửi bất kỳ gói nào. Phương pháp này yêu cầu bạn chọn một máy chủ bên ngoài cụ thể. Hầu hết thời gian, bất kỳ IP công cộng nổi tiếng nào cũng nên thực hiện thủ thuật. Tôi thích địa chỉ máy chủ DNS công cộng 8.8.8.8 của Google cho mục đích này, nhưng có thể đôi khi bạn muốn chọn một IP máy chủ bên ngoài khác. Dưới đây là một số mã minh họa cách tiếp cận đầy đủ.

void GetPrimaryIp(char* buffer, size_t buflen) 
{
    assert(buflen >= 16);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sock != -1);

    const char* kGoogleDnsIp = "8.8.8.8";
    uint16_t kDnsPort = 53;
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = inet_addr(kGoogleDnsIp);
    serv.sin_port = htons(kDnsPort);

    int err = connect(sock, (const sockaddr*) &serv, sizeof(serv));
    assert(err != -1);

    sockaddr_in name;
    socklen_t namelen = sizeof(name);
    err = getsockname(sock, (sockaddr*) &name, &namelen);
    assert(err != -1);

    const char* p = inet_ntop(AF_INET, &name.sin_addr, buffer, buflen);
    assert(p);

    close(sock);
}

2
điều này mang lại cho tôi ip 127 riêng tư. *. *. *. Có lẽ vì tôi đứng sau NAT?
eugene

14

Như bạn đã tìm ra, không có cái gọi là "địa chỉ IP cục bộ" duy nhất. Đây là cách tìm ra địa chỉ cục bộ có thể được gửi đến một máy chủ cụ thể.

  1. Tạo một ổ cắm UDP
  2. Kết nối ổ cắm với một địa chỉ bên ngoài (máy chủ cuối cùng sẽ nhận được địa chỉ cục bộ)
  3. Sử dụng getsockname để lấy địa chỉ cục bộ

Tôi chưa bao giờ làm theo cách này nhưng tôi thích nó. Nó sử dụng bảng định tuyến hệ điều hành tự động và dễ dàng hơn nhiều so với việc quét danh sách giao diện.
Zan Lynx

5
Điều này giả định rằng bạn biết địa chỉ bên ngoài sẽ là gì. Điều này thường không xảy ra trong các trung tâm dữ liệu.
Christopher

Bạn luôn có thể thử ba cái được chỉ định cho các lục địa khác nhau (IANA làm cho việc này trở nên dễ dàng) và chấp nhận tốt nhất hai trong ba.
Joshua

9

Điều này có lợi thế là làm việc trên nhiều hương vị của unix ... và bạn có thể sửa đổi nó một cách dễ dàng để hoạt động trên bất kỳ o / s nào. Tất cả các giải pháp ở trên cung cấp cho tôi lỗi trình biên dịch tùy thuộc vào giai đoạn của mặt trăng. Tại thời điểm có một cách POSIX tốt để làm điều đó ... không sử dụng điều này (tại thời điểm điều này được viết, đó không phải là trường hợp).

// ifconfig | perl -ne 'print "$1\n" if /inet addr:([\d.]+)/'

#include <stdlib.h>

int main() {
        setenv("LANG","C",1);
        FILE * fp = popen("ifconfig", "r");
        if (fp) {
                char *p=NULL, *e; size_t n;
                while ((getline(&p, &n, fp) > 0) && p) {
                   if (p = strstr(p, "inet ")) {
                        p+=5;
                        if (p = strchr(p, ':')) {
                            ++p;
                            if (e = strchr(p, ' ')) {
                                 *e='\0';
                                 printf("%s\n", p);
                            }
                        }
                   }
              }
        }
        pclose(fp);
        return 0;
}

cảm ơn bạn cho giải pháp này, hoạt động tuyệt vời đối với tôi và đi kèm rất tiện dụng trong nhiều tình huống khi bạn chỉ muốn để có được một số dữ liệu từ cl-chương trình
zitroneneis

Mặc dù ifconfig xuất ra dữ liệu cần thiết, nhưng tôi thực sự không khuyến khích sử dụng bất kỳ chương trình nào theo cách này. Sẽ tốt hơn nhiều nếu thực sự truy cập vào nguồn của ifconfig và có được chức năng hơn là chạy nó và phân tích cú pháp đầu ra của nó.
Zaur Nasibov

1
@MartinMeeser: đặt ngôn ngữ trung lập bằng cách tìm kiếm "inet" rồi tìm ":" riêng biệt.
Erik Aronesty

1
Sau đó, bạn có các ngôn ngữ khác thậm chí không phải là bộ ký tự dựa trên tiếng Latinh. Lưu ý là không sử dụng các công cụ lệnh, vì chúng sẽ không còn được dùng nữa ifconfig. Bạn nên sử dụng ipngay bây giờ. Cách thích hợp để tạo đầu ra trung lập ngôn ngữ là đặt LANGbiến môi trường thành C, như:LANG=C ls -l
Anders

1
Cảm ơn. Đó là một giải pháp dễ dàng và tuyệt vời!
Ahmad Afkandeh

5

Tôi không nghĩ rằng có một câu trả lời chắc chắn đúng cho câu hỏi của bạn. Thay vào đó, có rất nhiều cách làm thế nào để đạt được những gì bạn muốn. Do đó, tôi sẽ cung cấp một số gợi ý cách thực hiện nó.

Nếu một máy có nhiều hơn 2 giao diện ( lođược tính là một), bạn sẽ gặp khó khăn để tự động phát hiện giao diện phù hợp một cách dễ dàng. Dưới đây là một số công thức về cách thực hiện.

Ví dụ, vấn đề là nếu các máy chủ nằm trong DMZ phía sau tường lửa NAT thay đổi IP công cộng thành một số IP riêng và chuyển tiếp các yêu cầu. Máy của bạn có thể có 10 giao diện, nhưng chỉ một giao diện tương ứng với giao diện chung.

Ngay cả tính năng tự động phát hiện cũng không hoạt động trong trường hợp bạn đang sử dụng NAT kép, trong đó tường lửa của bạn thậm chí còn dịch IP nguồn thành một thứ hoàn toàn khác. Vì vậy, bạn thậm chí không thể chắc chắn, rằng tuyến đường mặc định dẫn đến giao diện của bạn với giao diện công khai.

Phát hiện nó thông qua tuyến đường mặc định

Đây là cách tôi đề xuất để tự động phát hiện mọi thứ

Một cái gì đó giống như ip r get 1.1.1.1thường cho bạn biết giao diện có tuyến đường mặc định.

Nếu bạn muốn tạo lại điều này bằng ngôn ngữ lập trình / kịch bản yêu thích của mình, hãy sử dụng strace ip r get 1.1.1.1và đi theo con đường gạch màu vàng.

Đặt nó với /etc/hosts

Đây là khuyến nghị của tôi nếu bạn muốn kiểm soát

Bạn có thể tạo một mục nhập /etc/hostsnhư

80.190.1.3 publicinterfaceip

Sau đó, bạn có thể sử dụng bí danh này publicinterfaceipđể tham chiếu đến giao diện công khai của mình.

Đáng buồn là haproxykhông tìm hiểu thủ thuật này với IPv6

Sử dụng môi trường

Đây là một giải pháp tốt /etc/hoststrong trường hợp bạn khôngroot

Giống như /etc/hosts. nhưng sử dụng môi trường cho việc này. Bạn có thể thử /etc/profilehoặc ~/.profilecho điều này.

Do đó, nếu chương trình của bạn cần một biến MYPUBLICIPthì bạn có thể bao gồm mã như (đây là C, hãy tạo C ++ từ nó):

#define MYPUBLICIPENVVAR "MYPUBLICIP"

const char *mypublicip = getenv(MYPUBLICIPENVVAR);

if (!mypublicip) { fprintf(stderr, "please set environment variable %s\n", MYPUBLICIPENVVAR); exit(3); }

Vì vậy, bạn có thể gọi tập lệnh / chương trình của mình /path/to/your/scriptnhư thế này

MYPUBLICIP=80.190.1.3 /path/to/your/script

điều này thậm chí hoạt động trong crontab.

Liệt kê tất cả các giao diện và loại bỏ những giao diện bạn không muốn

Cách tuyệt vọng nếu bạn không thể sử dụng ip

Nếu bạn biết những gì bạn không muốn, bạn có thể liệt kê tất cả các giao diện và bỏ qua tất cả các giao diện sai.

Đây dường như là một câu trả lời https://stackoverflow.com/a/265978/490291 cho cách tiếp cận này.

Làm điều đó như DLNA

Cách của một người đàn ông say rượu cố gắng nhấn chìm mình trong rượu

Bạn có thể thử liệt kê tất cả các cổng UPnP trên mạng của mình và bằng cách này, bạn có thể tìm ra lộ trình thích hợp cho một số thứ "bên ngoài". Điều này thậm chí có thể nằm trên một tuyến đường mà tuyến đường mặc định của bạn không trỏ đến.

Để biết thêm về điều này, có thể xem https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

Điều này mang lại cho bạn ấn tượng tốt về cái nào là giao diện công khai thực sự của bạn, ngay cả khi tuyến đường mặc định của bạn chỉ ở nơi khác.

Có nhiều hơn nữa

Nơi ngọn núi gặp nhà tiên tri

Các bộ định tuyến IPv6 tự quảng cáo để cung cấp cho bạn tiền tố IPv6 phù hợp. Nhìn vào tiền tố cho bạn gợi ý về việc nó có một số IP nội bộ hay một IP toàn cầu.

Bạn có thể nghe các khung IGMP hoặc IBGP để tìm ra một số cổng thích hợp.

Có ít hơn 2 ^ 32 địa chỉ IP. Do đó, không mất nhiều thời gian trên mạng LAN để ping tất cả chúng. Điều này cung cấp cho bạn một gợi ý thống kê về vị trí của phần lớn Internet theo quan điểm của bạn. Tuy nhiên, bạn nên hợp lý hơn một chút so với https://de.wikipedia.org/wiki/SQL_Slammer nổi tiếng

ICMP và thậm chí ARP là những nguồn tốt cho thông tin băng tần mạng. Nó cũng có thể giúp bạn.

Bạn có thể sử dụng địa chỉ Ethernet Broadcast để liên hệ với tất cả các thiết bị cơ sở hạ tầng mạng của mình, điều này thường sẽ hữu ích, như DHCP (thậm chí DHCPv6), v.v.

Danh sách bổ sung này có lẽ là vô tận và luôn không đầy đủ, bởi vì mọi nhà sản xuất thiết bị mạng đang bận rộn phát minh ra các lỗ hổng bảo mật mới về cách tự động phát hiện thiết bị của họ. Điều này thường giúp ích rất nhiều cho cách phát hiện một số giao diện công cộng mà không có giao diện nào.

'' Nuff nói. Ngoài.


4

Ngoài những gì Steve Baker đã nói, bạn có thể tìm thấy mô tả về ioctl SIOCGIFCONF trong trang người dùng netdevice (7) .

Khi bạn đã có danh sách tất cả các địa chỉ IP trên máy chủ, bạn sẽ phải sử dụng logic cụ thể của ứng dụng để lọc ra những địa chỉ bạn không muốn và hy vọng bạn còn lại một địa chỉ IP.


4

Đừng làm khó nó: đây là thứ có thể thay đổi. Nhiều chương trình tìm ra những gì cần liên kết bằng cách đọc trong một tệp cấu hình và thực hiện bất cứ điều gì nói. Bằng cách này, nếu chương trình của bạn một lúc nào đó trong tương lai cần phải liên kết với thứ gì đó không phải là IP công khai, nó có thể làm như vậy.


2

Như câu hỏi chỉ định Linux, kỹ thuật yêu thích của tôi để khám phá địa chỉ IP của một máy là sử dụng netlink. Bằng cách tạo một ổ cắm liên kết mạng của giao thức NETLINK_ROUTE và gửi RTM_GETADDR, ứng dụng của bạn sẽ nhận được (các) thông báo chứa tất cả các địa chỉ IP có sẵn. Một ví dụ được cung cấp ở đây .

Để đơn giản hóa các phần của việc xử lý tin nhắn, libmnl rất tiện lợi. Nếu bạn tò mò muốn tìm hiểu thêm về các tùy chọn khác nhau của NETLINK_ROUTE (và cách chúng được phân tích cú pháp), thì nguồn tốt nhất là mã nguồn của iproute2 (đặc biệt là ứng dụng màn hình) cũng như các hàm nhận trong hạt nhân. Trang chủ của rtnetlink cũng chứa thông tin hữu ích.


1

Bạn có thể thực hiện một số tích hợp với curl một cách dễ dàng như: curl www.whatismyip.orgtừ shell sẽ giúp bạn có được ip toàn cầu của mình. Bạn thuộc loại phụ thuộc vào một số máy chủ bên ngoài, nhưng bạn sẽ luôn như vậy nếu bạn đứng sau NAT.


Tôi không bao giờ hiểu tại sao nhiều người không sử dụng trang web này. Họ thậm chí còn có một trang có thể đọc được bằng máy để bạn không phải sàng lọc thông tin.
deft_code

1
Bạn có thể không nhận được IP chính xác nếu bạn đang hoạt động bên trong proxy.
Jack,

1
icanhazip.com trả lại IP và không có gì hơn. Hoàn hảo để nhúng vào một tập lệnh bash!
lukecyca

ipinfo.io/ip là một thay thế khác. Nếu bạn cuộn ipinfo.io, nó cũng trả về tên máy chủ, thông tin vị trí địa lý và hơn thế nữa ở định dạng JSON.
Ben Dowling


-10

// Use a HTTP request to a well known server that echo's back the public IP address void GetPublicIP(CString & csIP) { // Initialize COM bool bInit = false; if (SUCCEEDED(CoInitialize(NULL))) { // COM was initialized bInit = true;

    // Create a HTTP request object
    MSXML2::IXMLHTTPRequestPtr HTTPRequest;
    HRESULT hr = HTTPRequest.CreateInstance("MSXML2.XMLHTTP");
    if (SUCCEEDED(hr))
    {
        // Build a request to a web site that returns the public IP address
        VARIANT Async;
        Async.vt = VT_BOOL;
        Async.boolVal = VARIANT_FALSE;
        CComBSTR ccbRequest = L"http://whatismyipaddress.com/";

        // Open the request
        if (SUCCEEDED(HTTPRequest->raw_open(L"GET",ccbRequest,Async)))
        {
            // Send the request
            if (SUCCEEDED(HTTPRequest->raw_send()))
            {
                // Get the response
                CString csRequest = HTTPRequest->GetresponseText();

                // Parse the IP address
                CString csMarker = "<!-- contact us before using a script to get your IP address -->";
                int iPos = csRequest.Find(csMarker);
                if (iPos == -1)
                    return;
                iPos += csMarker.GetLength();
                int iPos2 = csRequest.Find(csMarker,iPos);
                if (iPos2 == -1)
                    return;

                // Build the IP address
                int nCount = iPos2 - iPos;
                csIP = csRequest.Mid(iPos,nCount);
            }
        }
    }
}

// Unitialize COM
if (bInit)
    CoUninitialize();

}


10
Đây là một giải pháp nặng, chỉ dành cho cửa sổ (câu hỏi về linux), có định dạng tồi, dựa vào các dịch vụ ngoài tầm kiểm soát cục bộ thông qua phân tích cú pháp nội dung trang web mỏng manh. Không thể tìm thấy mặt tích cực nào của "giải pháp" này.
viraptor

1
Hả! Phân tích cú pháp HTML thậm chí còn tìm kiếm một nhận xét là một cảnh báo về chính xác những gì đang được thực hiện.
notlesh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.