《Unix 網路程式設計》15:Unix 域協議

樵仙發表於2022-06-08

Unix 域協議

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

介紹

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

Unix 域協議

  • 並不是一個實際的協議組,而是在單個主機上執行 C/S 通訊的一種方式

  • 所用的 API 就是之前學習的套接字 API

  • 使用普通檔案系統中的路徑名標識協議地址

    這些路徑名不是普通的 Unix 檔案,除非把它們和 Unix 域套接字關聯起來,否則無法讀寫這些檔案

  • Unix 域協議可以被視為 IPC 方法之一

兩類套接字:

  • 位元組流套接字(類似 TCP)
  • 資料包套接字(類似 UDP)

為什麼要使用:

  1. :比位於同一主機的 TCP 快出一倍多
  2. 可以用在同一主機不同程式間傳遞描述符
  3. 把客戶的憑證提供給伺服器,從而可以提供額外的安全檢查措施

Unix 域套接字結構

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

結構說明

#include <sys/un.h>

struct sockaddr_un {
  sa_family_t sun_familly; // AF_LOCAL
  char sun_path[104];
}

sun_path

  1. 可見路徑的長度是有限制的,不同系統的長度可能不一致,可能在 92 ~ 108 之間
  2. 應該以 \0 結尾
  3. 如果沒有指定地址,則只儲存一個 \0 即可,等價於 IPv4 的 INADDR_ANY 以及 IPv6 的 IN6ADDR_ANY_INIT

sun_family

POSIX 為了推廣 Unix 域,而不僅僅是在 Unix 作業系統上使用,把它重新命名為 本地 IPC ,並把 sun_family 由 AF_UNIX 變為 AF_LOCAL ,但是我們仍然使用 Unix 域這個稱呼;

另外,儘管 POSIX 努力使它獨立於作業系統,它的套接字地址結構仍然保留 _un 字尾

案例:bind 呼叫

int main(int argc, char** argv) {
    int sockfd;
    socklen_t len;
    struct sockaddr_un addr1, addr2;

    if (argc != 2)
        err_quit("usage: unixbind <pathname>");

    sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);

    // 為了防止已經存在,我們先刪除這個路徑名
    // 如果不存在,會返回一個我們將要忽略的錯誤
    unlink(argv[1]); 

    bzero(&addr1, sizeof(addr1));
    addr1.sun_family = AF_LOCAL;

    // 複製命令列引數,如果過長則會截斷以免路徑名存不下
    // 由於 size - 1,所以保證了以 0 結尾
    strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1);

    // 繫結
    // 用 SUN_LEN 計算了 addr1 的長度
    Bind(sockfd, (SA*)&addr1, SUN_LEN(&addr1));

    len = sizeof(addr2);
    Getsockname(sockfd, (SA*)&addr2, &len);
    printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);

    exit(0);
}

測試:

[root@centos-5610 unixdomain]# ./unixbind /tmp/abc123
bound name = /tmp/abc123, returned len = 14

# 可以多次執行


<div class=anti_spider><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文資訊<th>本文資訊<th>防爬蟲替換資訊<tbody><tr><td><strong>作者網站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>聯絡方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文標題</strong><td>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園<td><code>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援</li><li>原文會不斷地<strong>更新和完善</strong>,<strong>排版和樣式會更加適合閱讀</strong>,並且<strong>有相關配圖</strong></li><li>如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div></div></div>

[root@centos-5610 unixdomain]# ./unixbind /tmp/abc123
bound name = /tmp/abc123, returned len = 14

# 用 ll 觀察一下是否存在該目錄,可以看到型別為 s


<div class=anti_spider><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文資訊<th>本文資訊<th>防爬蟲替換資訊<tbody><tr><td><strong>作者網站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>聯絡方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文標題</strong><td>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園<td><code>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援</li><li>原文會不斷地<strong>更新和完善</strong>,<strong>排版和樣式會更加適合閱讀</strong>,並且<strong>有相關配圖</strong></li><li>如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div></div></div>

[root@centos-5610 unixdomain]# ll /tmp 
total 0
srwxr-xr-x. 1 root root  0 Jun  7 02:35 abc123

# 如果名稱過長,會發生截斷


<div class=anti_spider><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文資訊<th>本文資訊<th>防爬蟲替換資訊<tbody><tr><td><strong>作者網站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>聯絡方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文標題</strong><td>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園<td><code>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援</li><li>原文會不斷地<strong>更新和完善</strong>,<strong>排版和樣式會更加適合閱讀</strong>,並且<strong>有相關配圖</strong></li><li>如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div></div></div>

# 這裡最後的長度為 5 + 102 + 1(\0) = 108,很正確


<div class=anti_spider><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div class=table-wrapper><table><thead><tr><th>本文資訊<th>本文資訊<th>防爬蟲替換資訊<tbody><tr><td><strong>作者網站</strong><td><a href=https://lymtics.top target=_blank>LYMTICS</a><td><code>https://lymtics.top</code><tr><td><strong>作者</strong><td>LYMTICS(樵仙)<td><code>https://lymtics.top</code><tr><td><strong>聯絡方式</strong><td>me@tencent.ml<td><code>me@tencent.ml</code><tr><td><strong>原文標題</strong><td>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園<td><code>《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園</code><tr><td><strong>原文地址</strong><td><a href=https://www.cnblogs.com/lymtics/p/16354451.html target=_blank>https://www.cnblogs.com/lymtics/p/16354451.html</a><td><code>https://www.cnblogs.com/lymtics/p/16354451.html</code></table><ul><li>如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援</li><li>原文會不斷地<strong>更新和完善</strong>,<strong>排版和樣式會更加適合閱讀</strong>,並且<strong>有相關配圖</strong></li><li>如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊</li></ul><div><mark>★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★</mark></div><div><img src=https://i.iter01.com/images/6dde0b3f24af672387085fceff81972ff5c3cd981642d03a0444761f29c86604.png loading=lazy></div></div></div>

[root@centos-5610 unixdomain]# ./unixbind /tmp/1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000aaaaaaaaaa
bound name = /tmp/1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000aa, returned len = 110

問題:

上述程式碼有一個問題,當發生截斷時,再次呼叫會報錯 bind error: Address already in usev

原因是作者的程式碼是先 unlink 再截斷的,所以說 unlink 的是那個長的,而不是截斷後的

關於 umask 的補充說明:

作者在執行上述程式碼前先呼叫了 umask,可以參考:umask 是什麼,下面是一個摘要:

使用者建立的檔案和目錄,都有預設的許可權,比如,檔案的預設許可權為0666,資料夾的預設許可權為0777,因為:

  • 建立檔案一般是用來讀寫,所以預設情況下所有使用者都具有讀寫許可權,但是沒有可執行許可權,所以檔案建立的預設許可權為0666
  • 而資料夾的 x 許可權表示的是開啟許可權,所以這個許可權必須要有,所以資料夾的預設許可權為0777

但是系統為了保護使用者建立檔案和資料夾的許可權,此時系統會有一個預設的使用者掩碼 umask,大多數的Linux系統的預設掩碼為022

使用者掩碼的作用是使用者在建立檔案時從檔案的預設許可權中去除掩碼中的許可權。所以檔案建立之後的許可權實際為:預設許可權 - umask,所以在使用者不修改umask的情況下,建立檔案的許可權為:0666-0022=0644。建立資料夾的許可權為:0777-0022=0755

socketpair 函式

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

socketpair 函式建立兩個隨後連線起來的套接字。本函式僅適用於 Unix 域套接字。

#include <sys/socket.h>

int socketpair(int family,		// AF_LOCAL
               int type,			// SOCK_STREAM 或 SOCK_DGRAM
               int protocal,	// 0
               int sockfd[2]	// 新建立的套接字描述符作為 sockfd[0] 和 sockfd[1] 返回
              );

這樣建立的兩個套接字不曾命名,也就是說其中沒有設計隱式的 bind 呼叫

指定 type 為 SOCK_STREAM 得到的結果稱為流管道,它與呼叫 pipe 建立的普通 Unix 管道類似,差別在於流管道是全雙工的,即兩個描述符都是既可讀又可寫。

套接字函式的差別

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

當用於 Unix 域套接字時,套接字函式中存在一些差異和限制:

  1. 由 bind 建立的路徑名預設訪問許可權應該是 0777,並按照當前 umask 值進行修正

  2. 與 Unix 域套接字關聯的路徑名應該是一個絕對路徑名,而不是一個相對路徑名

  3. 在 connect 呼叫中指定的路徑名必須是一個當前繫結在某個開啟的 Unix 域套接字上的路徑名,而且它們的套接字型別也必須一致

    如果該路徑不是 Unix 域套接字,或沒有與之關聯的開啟的描述符,或型別不符,就會報錯

  4. 呼叫 connect 連線一個 Unix 域套接字涉及的許可權等同於呼叫 open 以只寫方式訪問相應的路徑名

  5. Unix 域位元組流套接字類似 TCP 套接字:它們都提供無記錄邊界的位元組流介面

  6. 如果對於某個 Unix 域位元組流套接字的 connect 呼叫發現這個監聽套接字的佇列已滿,呼叫就立即返回一個 E.CONN.REFUSED 錯誤,這一點不同於 TCP:服務端忽略,客戶端重傳

  7. Unix 域資料包套接字類似 UDP 套接字:它們都提供一個保留記錄邊界的不可靠的資料包服務

  8. 在一個未繫結的 Unix 域套接字上傳送資料包不會自動給這個套接字捆綁一個路徑名,這一點和 UDP 不同;這意味著除非資料傳送端已經繫結一個路徑名到它的套接字,否則資料包接收端無法發回應答資料包;類似地,對於某個 Unix 域資料包套接字的 connect 呼叫不會給本套接字捆綁一個路徑名,這一點不同於 TCP 和 UDP

位元組流程式

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

服務端

伺服器程式就像整合了我們之前的 TCP 客戶端和剛剛說的 Unix 域套接字操作:

int main(int argc, char** argv) {
    int listenfd, connfd;
    pid_t childpid;
    socklen_t clilen;
  	// sockaddr_un 而不是 sockaddr,下同
    struct sockaddr_un cliaddr, servaddr;
    void sig_chld(int);

    // AF_LOCAL 指明建立 Unix 域套接字
    listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0);

    // UNIXSTR_PATH:預先定義好的路徑常量
    unlink(UNIXSTR_PATH);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXSTR_PATH);

    Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    Signal(SIGCHLD, sig_chld);

    for (;;) {
        clilen = sizeof(cliaddr);
        if ((connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
                continue; /* back to for() */
            else
                err_sys("accept error");
        }

        if ((childpid = Fork()) == 0) { /* child process */
            Close(listenfd);            /* close listening socket */
            str_echo(connfd);           /* process request */
            exit(0);
        }
        Close(connfd); /* parent closes connected socket */
    }
}

客戶端

int main(int argc, char** argv) {
    int sockfd;
    struct sockaddr_un servaddr;

    sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXSTR_PATH);

    Connect(sockfd, (SA*)&servaddr, sizeof(servaddr));

    str_cli(stdin, sockfd); /* do it all */

    exit(0);
}

資料包程式

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

服務端

int main(int argc, char** argv) {
    int sockfd;
    struct sockaddr_un servaddr, cliaddr;

    sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);

    unlink(UNIXDG_PATH);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);

    Bind(sockfd, (SA*)&servaddr, sizeof(servaddr));

    dg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr));
}

客戶端

int main(int argc, char** argv) {
    int sockfd;
    struct sockaddr_un cliaddr, servaddr;

    sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);

    bzero(&cliaddr, sizeof(cliaddr)); /* bind an address for us */
    cliaddr.sun_family = AF_LOCAL;
    strcpy(cliaddr.sun_path, tmpnam(NULL));

  	// 要手動 Bind !! UDP 則不用
    Bind(sockfd, (SA*)&cliaddr, sizeof(cliaddr));

    bzero(&servaddr, sizeof(servaddr)); /* fill in server's address */
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);

  	// 實際的傳送在這個函式裡
    dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr));

    exit(0);
}

如果沒有 Bind,那麼伺服器在 dg_echo 函式中的 recvfrom 呼叫將返回一個空路徑名,這個空路徑名將導致伺服器在呼叫 sendto 時發生錯誤:

[root@centos-5610 unixdomain]# ./unixdgserv01 
sendto error: Transport endpoint is not connected

描述符傳遞

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

之前的方法

之前我們可以讓父程式把描述符傳遞給子程式:

  • fork 呼叫返回之後,子程式共享父程式所有開啟的描述符
  • exec 呼叫執行之後,所有描述符通常保持開啟狀態不變

本章的方法

使用 Unix 域套接字:

  • 在兩個程式之間建立一個 Unix 域套接字,然後使用 sendmsg 跨這個套接字傳送一個特殊訊息
  • 這個訊息由核心來專門處理,會把開啟的描述符從傳送程式傳遞到接受程式

具體步驟如下:

  1. 建立一個 Unix 域套接字

    • 如果是子程式向父程式傳遞,父程式可以預先呼叫 socketpair 建立一個可用於在父子程式間交換描述符的流管道
    • 如果沒有親緣關係,則父程式必須建立一個 Unix 域套接字,bind 一個路徑名到該套接字以允許客戶程式 connect 到該套接字;然後客戶可以向伺服器傳送一個開啟某個描述符的請求,伺服器再把該描述符通過 Unix 域套接字傳遞迴客戶
  2. 傳送程式開啟一個描述符

    可以用 Unix 函式如:open、pipe、mkfifo、socket、accept 等建立一個描述符(而不只是檔案描述符)

  3. 傳送程式建立一個 msghdr 結構,將待傳遞的描述符作為輔助資料(msg_control)傳送

    傳送描述符會使描述符的引用計數增加1(回憶之前的 fork),因此,即使傳送程式在呼叫 sendmsg 之後但在接受程式呼叫 recvmsg 之前關閉了該描述符,對於接受程式而言它仍然保持開啟狀態。我們說這個描述符“在飛行中”(in flight)。

  4. 接受程式呼叫 recvmsg 接收這個描述符,並可能會分配一個新的描述符數字

案例演示

目標:

  • 有父、子兩個程式
  • 子程式開啟一個檔案,並把描述符傳遞給父程式
  • 父程式用這個描述符輸出檔案的內容

圖示:

程式碼:

mycat.c

int my_open(const char*, int);

int main(int argc, char** argv) {
    int fd, n;
    char buff[BUFFSIZE];

    if (argc != 2)
        err_quit("usage: mycat <pathname>");

    // 呼叫 my_open 開啟檔案
    // 如果改成 open,則是簡單地開啟一個檔案
    if ((fd = my_open(argv[1], O_RDONLY)) < 0)
        err_sys("cannot open %s", argv[1]);

    while ((n = Read(fd, buff, BUFFSIZE)) > 0)
        Write(STDOUT_FILENO, buff, n);

    exit(0);
}

myopen.c

int my_open(const char* pathname, int mode) {
    int fd, sockfd[2], status;
    pid_t childpid;
    char c, argsockfd[10], argmode[10];

    // 建立一個流管道,返回兩個描述符儲存在 sockfd 中
    Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);

    if ((childpid = Fork()) == 0) { /* child process */
        // 關閉 0 ,使用 1
        Close(sockfd[0]);
        // int snprintf(char *str, size_t size, const char *format, ...)
        // 將可變引數(...)按照 format 格式化成字串,並將字串複製到 str 
        // size 為要寫入的字元的最大數目,超過 size 會被截斷。
        snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);
        snprintf(argmode, sizeof(argmode), "%d", mode);
        // exec 的一種變型,之前提到過
        execl("./openfile", "openfile", argsockfd, pathname, argmode,
              (char*)NULL);
        err_sys("execl error");
    }

    // 如下為父程式的操作

    // 關閉 1,流下 0
    Close(sockfd[1]); /* close the end we don't use */

    // 等待子程式終止 waitpid 的知識前面提到過
    Waitpid(childpid, &status, 0);
    if (WIFEXITED(status) == 0)
        err_quit("child did not terminate");
    // 用 W.EXIT.STATUS 把終止狀態轉換成退出狀態
    if ((status = WEXITSTATUS(status)) == 0)
        // 我們自己寫的函式,他將通過流管道接收描述符
        // 除了描述符外,我們還讀取了一個位元組的資料,但是不對其進行任何處理
        Read_fd(sockfd[0], &c, 1, &fd);
    else {
        errno = status; /* set errno value from child's status */
        fd = -1;
    }

    Close(sockfd[0]);
    return (fd);

read_fd.c

ssize_t read_fd(int fd, void* ptr, size_t nbytes, int* recvfd) {
    struct msghdr msg;
    struct iovec iov[1];
    ssize_t n;

// 本函式必須處理兩個版本的 recvmsg:使用 msg_control 或 msg_accrights
// 前者會定義常量 HAVE_MSGHDR_MSG_CONTROL
#ifdef HAVE_MSGHDR_MSG_CONTROL
    // msg_control 緩衝區必須為 cmsghdr 結構適當地對齊,所以宣告這個聯合
    union {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr* cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
#else
    int newfd;

    msg.msg_accrights = (caddr_t)&newfd;
    msg.msg_accrightslen = sizeof(int);
#endif

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    // 接收資料
    if ((n = recvmsg(fd, &msg, 0)) <= 0)
        return (n);

#ifdef HAVE_MSGHDR_MSG_CONTROL
    // 對輔助資料進行格式驗證
    if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
        cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
        if (cmptr->cmsg_level != SOL_SOCKET)
            err_quit("control level != SOL_SOCKET");
        if (cmptr->cmsg_type != SCM_RIGHTS)
            err_quit("control type != SCM_RIGHTS");
        // 通過驗證,取出資料
        *recvfd = *((int*)CMSG_DATA(cmptr));
    } else
        *recvfd = -1; /* descriptor was not passed */
#else
    /* *INDENT-OFF* */
    if (msg.msg_accrightslen == sizeof(int))
        *recvfd = newfd;
    else
        *recvfd = -1; /* descriptor was not passed */
                      /* *INDENT-ON* */
#endif

    return (n);
}
/* end read_fd */

ssize_t Read_fd(int fd, void* ptr, size_t nbytes, int* recvfd) {
    ssize_t n;

    if ((n = read_fd(fd, ptr, nbytes, recvfd)) < 0)
        err_sys("read_fd error");

    return (n);
}

openfile.c

int main(int argc, char** argv) {
    int fd;

    if (argc != 4)
        err_quit("openfile <sockfd#> <filename> <mode>");

    if ((fd = open(argv[2], atoi(argv[3]))) < 0)
        exit((errno > 0) ? errno : 255);

    if (write_fd(atoi(argv[1]), "", 1, fd) < 0)
        exit((errno > 0) ? errno : 255);

    exit(0);
}

write_fd.c

ssize_t write_fd(int fd, void* ptr, size_t nbytes, int sendfd) {
    struct msghdr msg;
    struct iovec iov[1];

#ifdef HAVE_MSGHDR_MSG_CONTROL
    union {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr* cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int*)CMSG_DATA(cmptr)) = sendfd;
#else
    msg.msg_accrights = (caddr_t)&sendfd;
    msg.msg_accrightslen = sizeof(int);
#endif

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    // 傳送 
    return (sendmsg(fd, &msg, 0));
}
/* end write_fd */

ssize_t Write_fd(int fd, void* ptr, size_t nbytes, int sendfd) {
    ssize_t n;

    if ((n = write_fd(fd, ptr, nbytes, sendfd)) < 0)
        err_sys("write_fd error");

    return (n);
}

接收傳送者的憑證

《Unix 網路程式設計》15:Unix 域協議
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
本文資訊本文資訊防爬蟲替換資訊
作者網站LYMTICShttps://lymtics.top
作者LYMTICS(樵仙)https://lymtics.top
聯絡方式me@tencent.mlme@tencent.ml
原文標題《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園《Unix 網路程式設計》15:Unix 域協議 - 樵仙 - 部落格園
原文地址https://www.cnblogs.com/lymtics/p/16354451.htmlhttps://www.cnblogs.com/lymtics/p/16354451.html
  • 如果您看到了此內容,則本文可能是惡意爬取原作者的文章,建議返回原站閱讀,謝謝您的支援
  • 原文會不斷地更新和完善排版和樣式會更加適合閱讀,並且有相關配圖
  • 如果爬蟲破壞了上述連結,可以訪問 `lymtics.top` 獲取更多資訊
★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★
《Unix 網路程式設計》15:Unix 域協議

介紹

憑證傳遞仍然是一個尚未普及且無統一規範的特性,然而它是對 Unix 域協議的一個儘管簡單卻也重要的補充。

當客戶和伺服器進行通訊時,伺服器通常需要以一定的手段獲悉客戶的身份,以便驗證客戶是否有許可權請求相應的服務。

// FreeBSD
#include <sys/socket.h>

struct cmsgcred {
  pid_t	cmcred_pid;		// PID
  uid_t	cmcred_uid;		// real UID
  uid_t	cmcred_euid;	// effective UID
  gid_t	cmcred_gid;		// read GID of sending process
  short	cmcred_ngroups; // 組 數量
  gid_t	cmcred_groups[CMGROUP_MAX]; // 組
}

憑證資訊總是可以通過 Unix 域套接字在兩個程式之間傳遞,然而傳送程式在傳送它們時往往需要做特殊的封裝處理,接收程式接收它們時也往往需要特殊的接受處理。

例如,在 FreeBSD 系統中,接收程式只需在呼叫 recvmsg 同時提供一個足以存放憑證的輔助資料空間即可,而傳送程式呼叫 sendmsg 時必須作為輔助資料包含一個 cmsgcred 結構才會隨資料傳遞憑證。

需要注意的是,cmsgcred 結構雖然是使用者提供的,但是其內容卻是核心填寫的,傳送程式無法偽造,總而保證了通過 Unix 域套接字傳遞憑證來驗證使用者身份的可靠性。

案例

目的:

  • 改寫之前那個 Unix 域套接字回射程式
  • 讓客戶端在用 sendmsg 傳送訊息時額外攜帶 一個空的 cmsgcred 結構
  • 修改 strecho.c ,使其在應答前先用 read_cred 函式獲取使用者的憑證資訊

read_cred 程式碼如下:

readcred.c

#define CONTROL_LEN (sizeof(struct cmsghdr) + sizeof(struct cmsgcred))

ssize_t read_cred(int fd,
                  void* ptr,
                  size_t nbytes,
                  struct cmsgcred* cmsgcredptr  // 憑證
) {
    struct msghdr msg;
    struct iovec iov[1];
    char control[CONTROL_LEN];
    int n;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);
    msg.msg_flags = 0;

    if ((n = recvmsg(fd, &msg, 0)) < 0)
        return (n);

    cmsgcredptr->cmcred_ngroups = 0; /* indicates no credentials returned */
    // 如果有憑證返回
    if (cmsgcredptr && msg.msg_controllen > 0) {
        struct cmsghdr* cmptr = (struct cmsghdr*)control;

        // 對其長度、等級、型別進行驗證
        if (cmptr->cmsg_len < CONTROL_LEN)
            err_quit("control length = %d", cmptr->cmsg_len);
        if (cmptr->cmsg_level != SOL_SOCKET)
            err_quit("control level != SOL_SOCKET");
        if (cmptr->cmsg_type != SCM_CREDS)
            err_quit("control type != SCM_CREDS");
        // 通過驗證,則複製到 cmsgcred 結構中
        memcpy(cmsgcredptr, CMSG_DATA(cmptr), sizeof(struct cmsgcred));
    }

    return (n);
}

strecho.c 改寫如下:

ssize_t read_cred(int, void*, size_t, struct cmsgcred*);

void str_echo(int sockfd) {
    ssize_t n;
    int i;
    char buf[MAXLINE];
    struct cmsgcred cred;

again:
    while ((n = read_cred(sockfd, buf, MAXLINE, &cred)) > 0) {
        if (cred.cmcred_ngroups == 0) {
            printf("(no credentials returned)\n");
        } else {
            printf("PID of sender = %d\n", cred.cmcred_pid);
            printf("real user ID = %d\n", cred.cmcred_uid);
            printf("real group ID = %d\n", cred.cmcred_gid);
            printf("effective user ID = %d\n", cred.cmcred_euid);
            printf("%d groups:", cred.cmcred_ngroups - 1);
            for (i = 1; i < cred.cmcred_ngroups; i++)
                printf(" %d", cred.cmcred_groups[i]);
            printf("\n");
        }
        Writen(sockfd, buf, n);
    }

    if (n < 0 && errno == EINTR)
        goto again;
    else if (n < 0)
        err_sys("str_echo: read error");
}

可以用 id 如下命令檢視個人的當前憑證:

[root@centos-5610 unixdomain]# id
uid=0(root) gid=0(root) groups=0(root) ...

相關文章