Apa perbedaan antara SO_REUSEADDR dan SO_REUSEPORT?

663

The man pagesdan dokumentasi programmer untuk opsi socket SO_REUSEADDRdan SO_REUSEPORTberbeda untuk sistem operasi yang berbeda dan sering sangat membingungkan. Beberapa sistem operasi bahkan tidak memiliki opsi SO_REUSEPORT. WEB penuh dengan kontradiksi informasi mengenai subjek ini dan seringkali Anda dapat menemukan informasi yang hanya berlaku untuk satu soket implementasi sistem operasi tertentu, yang bahkan mungkin tidak disebutkan secara eksplisit dalam teks.

Jadi bagaimana sebenarnya SO_REUSEADDRberbeda dari SO_REUSEPORT?

Apakah sistem tanpa SO_REUSEPORTlebih terbatas?

Dan apa sebenarnya perilaku yang diharapkan jika saya menggunakan salah satunya pada sistem operasi yang berbeda?

Mecki
sumber

Jawaban:

1616

Selamat datang di dunia portabilitas yang indah ... atau lebih tepatnya kekurangannya. Sebelum kita mulai menganalisis dua opsi ini secara terperinci dan melihat lebih dalam bagaimana berbagai sistem operasi menanganinya, perlu dicatat bahwa implementasi soket BSD adalah induk dari semua implementasi soket. Pada dasarnya semua sistem lain menyalin implementasi soket BSD di beberapa titik waktu (atau setidaknya interface-nya) dan kemudian mulai mengembangkannya sendiri. Tentu saja implementasi soket BSD juga dikembangkan pada saat yang sama dan dengan demikian sistem yang menyalinnya kemudian mendapatkan fitur yang kurang dalam sistem yang menyalinnya sebelumnya. Memahami implementasi soket BSD adalah kunci untuk memahami semua implementasi soket lainnya, jadi Anda harus membacanya bahkan jika Anda tidak peduli untuk pernah menulis kode untuk sistem BSD.

Ada beberapa dasar yang harus Anda ketahui sebelum kita melihat dua opsi ini. Koneksi TCP / UDP diidentifikasi oleh tuple dari lima nilai:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

Kombinasi unik dari nilai-nilai ini mengidentifikasi koneksi. Akibatnya, tidak ada dua koneksi yang dapat memiliki nilai lima yang sama, jika tidak, sistem tidak akan dapat lagi membedakan koneksi ini.

Protokol soket diatur ketika soket dibuat dengan socket()fungsi. Alamat sumber dan port diatur dengan bind()fungsi. Alamat dan port tujuan diatur dengan connect()fungsi. Karena UDP adalah protokol tanpa koneksi, soket UDP dapat digunakan tanpa menghubungkannya. Namun diperbolehkan untuk menghubungkannya dan dalam beberapa kasus sangat menguntungkan untuk kode Anda dan desain aplikasi umum. Dalam mode tanpa koneksi, soket UDP yang tidak terikat secara eksplisit ketika data dikirim untuk pertama kali biasanya secara otomatis terikat oleh sistem, karena soket UDP yang tidak terikat tidak dapat menerima data (balasan) apa pun. Hal yang sama berlaku untuk soket TCP yang tidak terikat, ia secara otomatis terikat sebelum akan terhubung.

Jika Anda secara eksplisit mengikat soket, Anda dapat mengikatnya ke port 0, yang berarti "port apa saja". Karena soket tidak dapat benar-benar terikat ke semua port yang ada, sistem harus memilih port spesifik itu sendiri dalam hal itu (biasanya dari kisaran port sumber yang telah ditentukan sebelumnya, OS spesifik). Wildcard serupa ada untuk alamat sumber, yang bisa "alamat apa saja" ( 0.0.0.0dalam kasus IPv4 dan::dalam hal IPv6). Tidak seperti port, soket benar-benar dapat diikat ke "alamat apa pun" yang berarti "semua alamat IP sumber dari semua antarmuka lokal". Jika soket tersambung kemudian, sistem harus memilih alamat IP sumber tertentu, karena soket tidak dapat dihubungkan dan pada saat yang sama terikat ke alamat IP lokal apa pun. Bergantung pada alamat tujuan dan konten dari tabel routing, sistem akan memilih alamat sumber yang sesuai dan mengganti ikatan "apa saja" dengan yang mengikat ke alamat IP sumber yang dipilih.

Secara default, tidak ada dua soket yang dapat terikat pada kombinasi alamat sumber dan port sumber yang sama. Selama port sumber berbeda, alamat sumber sebenarnya tidak relevan. Mengikat socketAuntuk A:Xdan socketBuntuk B:Y, di mana Adan Badalah alamat dan Xdan Yadalah port, selalu mungkin selama X != Yberlaku. Namun, bahkan jika X == Y, pengikatan masih dimungkinkan selama A != Bbenar. Misalnya socketAmilik program server FTP dan terikat 192.168.0.1:21dan socketBmilik program server FTP lain dan terikat 10.0.0.1:21, kedua binding akan berhasil. Perlu diingat, bahwa soket dapat diikat secara lokal ke "alamat apa pun". Jika soket terikat0.0.0.0:21, ia terikat ke semua alamat lokal yang ada pada saat yang sama dan dalam hal ini tidak ada soket lain yang dapat terikat ke port 21, terlepas dari alamat IP spesifik mana ia mencoba untuk mengikat, karena 0.0.0.0konflik dengan semua alamat IP lokal yang ada.

Apa pun yang dikatakan sejauh ini hampir sama untuk semua sistem operasi utama. Hal-hal mulai mendapatkan spesifik OS ketika penggunaan kembali alamat mulai berlaku. Kita mulai dengan BSD, karena seperti yang saya katakan di atas, itu adalah ibu dari semua implementasi soket.

BSD

SO_REUSEADDR

Jika SO_REUSEADDRdiaktifkan pada soket sebelum mengikatnya, soket dapat berhasil diikat kecuali ada konflik dengan soket lain yang terikat dengan kombinasi yang sama persis antara alamat dan port sumber. Sekarang Anda mungkin bertanya-tanya bagaimana bedanya dengan yang sebelumnya? Kata kunci adalah "tepat". SO_REUSEADDRterutama mengubah cara perlakuan alamat wildcard ("alamat IP") saat mencari konflik.

Tanpa SO_REUSEADDR, mengikat socketAuntuk 0.0.0.0:21kemudian mengikat socketBke 192.168.0.1:21akan gagal (dengan kesalahan EADDRINUSE), karena 0.0.0.0 berarti "alamat IP lokal", sehingga semua alamat IP lokal dianggap digunakan oleh socket ini dan ini termasuk 192.168.0.1juga. Dengan SO_REUSEADDRitu akan berhasil, karena 0.0.0.0dan 192.168.0.1yang tidak persis alamat yang sama, satu adalah wildcard untuk semua alamat lokal dan yang lain adalah alamat lokal yang sangat spesifik. Perhatikan bahwa pernyataan di atas benar terlepas dari urutannya socketAdan socketBterikat; tanpanya SO_REUSEADDRakan selalu gagal, dengan SO_REUSEADDRitu akan selalu berhasil.

Untuk memberi Anda gambaran umum yang lebih baik, mari buat tabel di sini dan buat daftar semua kemungkinan kombinasi:

SO_REUSEADDR socketA socketB Hasil
-------------------------------------------------- -------------------
  ON / OFF 192.168.0.1:21 192.168.0.1:21 Kesalahan (EADDRINUSE)
  ON / OFF 192.168.0.1:21 10.0.0.1:21 OK
  ON / OFF 10.0.0.1:21 192.168.0.1:21 OK
   OFF 0.0.0.0:21 192.168.1.0:21 Kesalahan (EADDRINUSE)
   OFF 192.168.1.0:21 0.0.0.0:21 Kesalahan (EADDRINUSE)
   ON 0.0.0.0:21 192.168.1.0:21 OK
   ON 192.168.1.0:21 0.0.0.0:21 OK
  ON / OFF 0,0.0.0:21 0.0.0.0:21 Kesalahan (EADDRINUSE)

Tabel di atas mengasumsikan bahwa socketAtelah berhasil terikat ke alamat yang diberikan untuk socketA, kemudian socketBdibuat, apakah akan SO_REUSEADDRditetapkan atau tidak, dan akhirnya terikat ke alamat yang diberikan untuk socketB. Resultadalah hasil dari operasi ikat untuk socketB. Jika kolom pertama mengatakan ON/OFF, nilai SO_REUSEADDRtidak relevan dengan hasilnya.

Oke, SO_REUSEADDRberpengaruh pada alamat wildcard, bagus untuk diketahui. Namun bukan itu efeknya saja. Ada efek terkenal lainnya yang juga merupakan alasan mengapa kebanyakan orang menggunakan SO_REUSEADDRprogram server di tempat pertama. Untuk penggunaan penting lainnya dari opsi ini, kita harus melihat lebih dalam tentang cara kerja protokol TCP.

Soket memiliki buffer pengiriman dan jika panggilan ke send()fungsi berhasil, itu tidak berarti bahwa data yang diminta benar-benar telah dikirim, itu hanya berarti data telah ditambahkan ke buffer pengirim. Untuk soket UDP, data biasanya dikirim segera, jika tidak segera, tetapi untuk soket TCP, mungkin ada penundaan yang relatif lama antara menambahkan data ke buffer pengiriman dan membuat implementasi TCP benar-benar mengirim data itu. Akibatnya, ketika Anda menutup soket TCP, mungkin masih ada data yang tertunda di buffer pengiriman, yang belum dikirim tetapi kode Anda menganggapnya sudah terkirim, karenasend()panggilan berhasil. Jika implementasi TCP langsung menutup soket atas permintaan Anda, semua data ini akan hilang dan kode Anda bahkan tidak akan tahu tentang itu. TCP dikatakan sebagai protokol yang andal dan kehilangan data begitu saja tidak terlalu bisa diandalkan. Itu sebabnya soket yang masih memiliki data untuk dikirim akan masuk ke keadaan yang disebut TIME_WAITketika Anda menutupnya. Dalam keadaan itu akan menunggu sampai semua data yang tertunda telah berhasil dikirim atau sampai batas waktu habis, dalam hal ini soket ditutup dengan paksa.

Jumlah waktu kernel akan menunggu sebelum menutup soket, terlepas dari apakah masih ada data dalam penerbangan atau tidak, disebut Linger Time . The Linger Waktu adalah global dikonfigurasi pada kebanyakan sistem dan secara default agak panjang (dua menit adalah nilai umum Anda akan menemukan pada banyak sistem). Ini juga dapat dikonfigurasi per soket menggunakan opsi soket SO_LINGERyang dapat digunakan untuk membuat waktu habis lebih pendek atau lebih lama, dan bahkan untuk menonaktifkannya sepenuhnya. Menonaktifkan sepenuhnya adalah ide yang sangat buruk, karena menutup soket TCP dengan anggun adalah proses yang sedikit rumit dan melibatkan pengiriman dan pengembalian beberapa paket (serta mengirim ulang paket-paket tersebut jika hilang) dan seluruh proses penutupan ini juga dibatasi oleh Waktu Berlama-lama. Jika Anda menonaktifkan sisa, soket Anda mungkin tidak hanya kehilangan data dalam penerbangan, itu juga selalu ditutup dengan paksa alih-alih dengan anggun, yang biasanya tidak disarankan. Rincian tentang bagaimana koneksi TCP ditutup dengan anggun berada di luar cakupan jawaban ini, jika Anda ingin mempelajari lebih lanjut, saya sarankan Anda melihat halaman ini . Dan bahkan jika Anda menonaktifkan berlama-lama dengan SO_LINGER, jika proses Anda mati tanpa secara eksplisit menutup soket, BSD (dan mungkin sistem lain) akan tetap ada, mengabaikan apa yang telah Anda konfigurasikan. Ini akan terjadi misalnya jika kode Anda hanya panggilanexit()(cukup umum untuk program server yang kecil dan sederhana) atau proses tersebut dimatikan oleh sinyal (yang mencakup kemungkinan bahwa itu hanya crash karena akses memori ilegal). Jadi tidak ada yang dapat Anda lakukan untuk memastikan soket tidak akan pernah tertinggal dalam semua keadaan.

Pertanyaannya adalah, bagaimana sistem memperlakukan soket dalam keadaan TIME_WAIT? Jika SO_REUSEADDRtidak disetel, soket dalam keadaan TIME_WAITdianggap masih terikat ke alamat sumber dan port dan setiap upaya untuk mengikat soket baru ke alamat dan port yang sama akan gagal sampai soket benar-benar ditutup, yang mungkin memakan waktu lama sebagai Linger Time yang dikonfigurasi . Jadi jangan berharap bahwa Anda dapat mengubah alamat sumber soket segera setelah menutupnya. Dalam kebanyakan kasus ini akan gagal. Namun, jika SO_REUSEADDRdiatur untuk soket yang Anda coba ikat, soket lain terikat ke alamat dan port yang sama dalam keadaanTIME_WAITdiabaikan, setelah semua yang sudah "setengah mati", dan soket Anda dapat mengikat ke alamat yang sama tanpa masalah. Dalam hal ini tidak ada peran yang soket lainnya mungkin memiliki alamat dan port yang sama persis. Perhatikan bahwa mengikat soket ke alamat dan port yang sama persis dengan soket sekarat di TIME_WAITnegara bagian dapat memiliki efek samping yang tidak diharapkan, dan biasanya tidak diinginkan, seandainya soket lainnya masih "bekerja", tetapi itu berada di luar cakupan jawaban ini dan untungnya efek samping tersebut agak jarang dalam praktek.

Ada satu hal terakhir yang harus Anda ketahui SO_REUSEADDR. Segala sesuatu yang tertulis di atas akan berfungsi selama soket yang ingin Anda ikat telah mengaktifkan kembali alamat. Tidak perlu bahwa soket lain, yang sudah terikat atau dalam TIME_WAITkeadaan, juga memiliki bendera ini ditetapkan ketika terikat. Kode yang memutuskan apakah ikatan akan berhasil atau gagal hanya memeriksa SO_REUSEADDRbendera soket dimasukkan ke dalam bind()panggilan, untuk semua soket lainnya diperiksa, bendera ini bahkan tidak melihat.

SO_REUSEPORT

SO_REUSEPORTadalah apa yang diharapkan kebanyakan orang SO_REUSEADDR. Pada dasarnya, SO_REUSEPORTmemungkinkan Anda untuk mengikat jumlah sewenang-wenang soket untuk persis alamat sumber yang sama dan port asalkan semua soket terikat sebelum juga telah SO_REUSEPORTditetapkan sebelum mereka terikat. Jika soket pertama yang terikat pada alamat dan port tidak SO_REUSEPORTdiatur, tidak ada soket lain yang dapat diikat ke alamat dan port yang sama persis, terlepas dari apakah soket lain ini telah SO_REUSEPORTdisetel atau tidak, sampai soket pertama melepaskan ikatannya lagi. Tidak seperti dalam kasus SO_REUESADDRpenanganan kode SO_REUSEPORTtidak hanya akan memverifikasi bahwa soket terikat saat ini telah SO_REUSEPORTditetapkan tetapi juga akan memverifikasi bahwa soket dengan alamat dan port yang saling bertentangan telah SO_REUSEPORTditetapkan ketika terikat.

SO_REUSEPORTtidak menyiratkan SO_REUSEADDR. Ini berarti jika soket tidak SO_REUSEPORTdiatur ketika terikat dan soket lain telah SO_REUSEPORTditetapkan ketika terikat ke alamat dan port yang sama persis, ikatan gagal, yang diharapkan, tetapi juga gagal jika soket lainnya sudah sekarat dan dalam TIME_WAITkeadaan. Untuk dapat mengikat soket ke alamat dan port yang sama seperti soket lain dalam TIME_WAITkeadaan mengharuskan SO_REUSEADDRuntuk ditetapkan pada soket itu atau SO_REUSEPORTharus telah ditetapkan pada kedua soket sebelum mengikatnya. Tentu saja diperbolehkan untuk mengatur keduanya, SO_REUSEPORTdan SO_REUSEADDR, pada soket.

Tidak ada banyak yang bisa dikatakan tentang SO_REUSEPORTselain itu ditambahkan kemudian SO_REUSEADDR, itu sebabnya Anda tidak akan menemukannya di banyak implementasi soket dari sistem lain, yang "bercabang" kode BSD sebelum opsi ini ditambahkan, dan bahwa tidak ada cara untuk mengikat dua soket ke alamat soket yang sama persis di BSD sebelum opsi ini.

Connect () Mengembalikan EADDRINUSE?

Sebagian besar orang tahu bahwa bind()mungkin gagal dengan kesalahan EADDRINUSE, namun, ketika Anda mulai bermain-main dengan penggunaan kembali alamat, Anda mungkin mengalami situasi aneh yang connect()gagal dengan kesalahan itu juga. Bagaimana ini bisa terjadi? Bagaimana alamat jarak jauh, setelah semua itu yang terhubung menambah soket, sudah bisa digunakan? Menghubungkan banyak soket ke alamat jarak jauh yang persis sama tidak pernah menjadi masalah sebelumnya, jadi apa yang salah di sini?

Seperti yang saya katakan di bagian paling atas dari balasan saya, koneksi didefinisikan oleh tuple dari lima nilai, ingat? Dan saya juga berkata, bahwa kelima nilai ini harus unik jika tidak, sistem tidak dapat lagi membedakan dua koneksi, kan? Nah, dengan penggunaan kembali alamat, Anda dapat mengikat dua soket protokol yang sama ke alamat sumber dan port yang sama. Itu berarti tiga dari lima nilai tersebut sudah sama untuk dua soket ini. Jika sekarang Anda mencoba menghubungkan kedua soket ini juga ke alamat dan port tujuan yang sama, Anda akan membuat dua soket yang terhubung, yang tupelnya benar-benar identik. Ini tidak bisa, paling tidak untuk koneksi TCP (koneksi UDP sebenarnya bukan koneksi yang sebenarnya). Jika data tiba untuk salah satu dari dua koneksi, sistem tidak dapat menentukan koneksi milik data tersebut.

Jadi jika Anda mengikat dua soket protokol yang sama ke alamat sumber dan port yang sama dan mencoba menghubungkan keduanya ke alamat tujuan dan port yang sama, connect()sebenarnya akan gagal dengan kesalahan EADDRINUSEuntuk soket kedua yang Anda coba sambungkan, yang berarti bahwa soket dengan tuple identik dari lima nilai sudah terhubung.

Alamat Multicast

Kebanyakan orang mengabaikan fakta bahwa alamat multicast ada, tetapi memang ada. Sementara alamat unicast digunakan untuk komunikasi satu-ke-satu, alamat multicast digunakan untuk komunikasi satu-ke-banyak. Kebanyakan orang mengetahui alamat multicast ketika mereka mengetahui tentang IPv6 tetapi alamat multicast juga ada di IPv4, meskipun fitur ini tidak pernah digunakan secara luas di Internet publik.

Arti SO_REUSEADDRperubahan untuk alamat multicast karena memungkinkan beberapa soket terikat ke kombinasi yang sama persis dari alamat dan port multicast sumber. Dengan kata lain, untuk alamat multicast SO_REUSEADDRberperilaku sama seperti SO_REUSEPORTuntuk alamat unicast. Sebenarnya, kode memperlakukan SO_REUSEADDRdan SO_REUSEPORTidentik untuk alamat multicast, itu berarti Anda bisa mengatakan itu SO_REUSEADDRmenyiratkan SO_REUSEPORTuntuk semua alamat multicast dan sebaliknya.


FreeBSD / OpenBSD / NetBSD

Semua ini adalah garpu yang agak terlambat dari kode BSD asli, itu sebabnya mereka bertiga menawarkan opsi yang sama dengan BSD dan mereka juga berperilaku seperti di BSD.


macOS (MacOS X)

Pada intinya, macOS hanyalah sebuah UNIX gaya BSD bernama " Darwin ", berdasarkan garpu yang agak terlambat dari kode BSD (BSD 4.3), yang kemudian disinkronkan lagi dengan FreeBSD (pada waktu itu) 5 basis kode untuk rilis Mac OS 10.3, sehingga Apple bisa mendapatkan kepatuhan POSIX penuh (macOS adalah POSIX bersertifikat). Meskipun memiliki microkernel pada intinya (" Mach "), sisa dari kernel (" XNU ") pada dasarnya hanya sebuah kernel BSD, dan itulah sebabnya macOS menawarkan opsi yang sama seperti BSD dan mereka juga berperilaku sama seperti pada BSD .

iOS / watchOS / tvOS

iOS hanyalah garpu macOS dengan kernel yang sedikit dimodifikasi dan dipangkas, agak dipangkas toolset ruang pengguna dan set kerangka kerja standar yang sedikit berbeda. watchOS dan tvOS adalah garpu iOS, yang dipreteli lebih jauh (terutama watchOS). Sepengetahuan terbaik saya, mereka semua berperilaku persis seperti halnya macOS.


Linux

Linux <3.9

Sebelum ke Linux 3.9, hanya opsi yang SO_REUSEADDRada. Opsi ini berlaku secara umum sama seperti di BSD dengan dua pengecualian penting:

  1. Selama soket TCP mendengarkan (server) terikat ke port tertentu, SO_REUSEADDRopsi ini sepenuhnya diabaikan untuk semua soket yang menargetkan port itu. Mengikat soket kedua ke port yang sama hanya mungkin jika itu juga mungkin di BSD tanpa harus SO_REUSEADDRmengatur. Misalnya Anda tidak dapat mengikat ke alamat wildcard dan kemudian ke yang lebih spesifik atau sebaliknya, keduanya dimungkinkan di BSD jika Anda atur SO_REUSEADDR. Apa yang dapat Anda lakukan adalah Anda dapat mengikat ke port yang sama dan dua alamat non-wildcard yang berbeda, seperti yang selalu diperbolehkan. Dalam aspek ini Linux lebih membatasi daripada BSD.

  2. Pengecualian kedua adalah bahwa untuk soket klien, opsi ini berperilaku persis seperti SO_REUSEPORTdi BSD, selama keduanya telah menetapkan flag ini sebelum terikat. Alasan untuk mengizinkannya adalah karena penting untuk dapat mengikat beberapa soket dengan tepat ke alamat soket UDP yang sama untuk berbagai protokol dan karena SO_REUSEPORTsebelumnya tidak ada sebelum 3.9, perilaku SO_REUSEADDRdiubah sesuai untuk mengisi celah tersebut . Dalam aspek itu, Linux tidak seketat BSD.

Linux> = 3.9

Linux 3.9 menambahkan opsi SO_REUSEPORTke Linux juga. Opsi ini berperilaku persis seperti opsi di BSD dan memungkinkan pengikatan ke alamat dan nomor port yang sama persis selama semua soket memiliki opsi ini ditetapkan sebelum mengikatnya.

Namun, masih ada dua perbedaan SO_REUSEPORTpada sistem lain:

  1. Untuk mencegah "pembajakan port", ada satu batasan khusus: Semua soket yang ingin berbagi alamat dan kombinasi port yang sama harus dimiliki oleh proses yang memiliki ID pengguna efektif yang sama! Jadi satu pengguna tidak bisa "mencuri" port dari pengguna lain. Ini adalah sihir khusus untuk mengimbangi hilang SO_EXCLBIND/ SO_EXCLUSIVEADDRUSEbendera.

  2. Selain itu kernel melakukan beberapa "sihir khusus" untuk SO_REUSEPORTsoket yang tidak ditemukan di sistem operasi lain: Untuk soket UDP, ia mencoba untuk mendistribusikan datagram secara merata, untuk soket pendengar TCP, ia mencoba untuk mendistribusikan permintaan koneksi masuk (yang diterima dengan menelepon accept()) merata di semua soket yang berbagi alamat dan kombinasi port yang sama. Dengan demikian aplikasi dapat dengan mudah membuka port yang sama dalam beberapa proses anak dan kemudian gunakan SO_REUSEPORTuntuk mendapatkan load balancing yang sangat murah.


Android

Meskipun keseluruhan sistem Android agak berbeda dari kebanyakan distribusi Linux, pada intinya bekerja kernel Linux yang sedikit dimodifikasi, sehingga segala sesuatu yang berlaku untuk Linux harus berlaku untuk Android juga.


Windows

Windows hanya tahu SO_REUSEADDRpilihannya, tidak ada SO_REUSEPORT. Pengaturan SO_REUSEADDRpada soket di Windows berperilaku seperti pengaturan SO_REUSEPORTdan SO_REUSEADDRpada soket di BSD, dengan satu pengecualian: Soket dengan SO_REUSEADDRselalu dapat mengikat ke alamat sumber dan port yang sama persis dengan soket yang sudah terikat, bahkan jika soket lainnya tidak memiliki opsi ini. atur kapan itu diikat . Perilaku ini agak berbahaya karena memungkinkan aplikasi "mencuri" port yang terhubung dari aplikasi lain. Tak perlu dikatakan, ini dapat memiliki implikasi keamanan besar. Microsoft menyadari bahwa ini mungkin menjadi masalah dan dengan demikian menambahkan opsi soket lainnya SO_EXCLUSIVEADDRUSE. PengaturanSO_EXCLUSIVEADDRUSEpada soket memastikan bahwa jika pengikatan berhasil, kombinasi alamat sumber dan port dimiliki secara eksklusif oleh soket ini dan tidak ada soket lain yang dapat mengikatnya, bahkan jika telah SO_REUSEADDRdiatur.

Untuk detail lebih lanjut tentang bagaimana flag SO_REUSEADDRdan SO_EXCLUSIVEADDRUSEbekerja di Windows, bagaimana mereka mempengaruhi binding / re-binding, Microsoft dengan ramah menyediakan tabel yang mirip dengan tabel saya di dekat bagian atas balasan itu. Cukup kunjungi halaman ini dan gulirkan sedikit ke bawah. Sebenarnya ada tiga tabel, yang pertama menunjukkan perilaku lama (sebelum Windows 2003), yang kedua perilaku (Windows 2003 dan lebih tinggi) dan yang ketiga menunjukkan bagaimana perilaku berubah di Windows 2003 dan kemudian jika bind()panggilan dilakukan oleh pengguna yang berbeda.


Solaris

Solaris adalah penerus SunOS. SunOS awalnya didasarkan pada garpu BSD, SunOS 5 dan kemudian didasarkan pada garpu SVR4, namun SVR4 merupakan gabungan dari BSD, System V, dan Xenix, sehingga sampai tingkat tertentu Solaris juga merupakan garpu BSD, dan yang agak awal. Akibatnya Solaris hanya tahu SO_REUSEADDR, tidak ada SO_REUSEPORT. The SO_REUSEADDRberperilaku hampir sama seperti halnya di BSD. Sejauh yang saya tahu tidak ada cara untuk mendapatkan perilaku yang sama seperti SO_REUSEPORTdi Solaris, itu berarti tidak mungkin untuk mengikat dua soket ke alamat dan port yang sama persis.

Mirip dengan Windows, Solaris memiliki opsi untuk memberi soket pengikatan eksklusif. Opsi ini dinamai SO_EXCLBIND. Jika opsi ini diatur pada soket sebelum mengikatnya, pengaturan SO_REUSEADDRpada soket lain tidak akan berpengaruh jika kedua soket diuji untuk konflik alamat. Misalnya jika socketAterikat ke alamat wildcard dan socketBtelah SO_REUSEADDRdiaktifkan dan terikat ke alamat non-wildcard dan port yang sama dengan socketA, ikatan ini biasanya akan berhasil, kecuali jika socketAtelah SO_EXCLBINDdiaktifkan, dalam hal ini akan gagal terlepas dari SO_REUSEADDRbendera socketB.


Sistem Lainnya

Jika sistem Anda tidak tercantum di atas, saya menulis sebuah program uji kecil yang dapat Anda gunakan untuk mengetahui bagaimana sistem Anda menangani dua opsi ini. Jika menurut Anda hasil saya salah , jalankan dulu program itu sebelum mengeposkan komentar dan mungkin membuat klaim palsu.

Semua yang diperlukan oleh kode untuk dibuat adalah API POSIX sedikit (untuk bagian-bagian jaringan) dan kompiler C99 (sebenarnya sebagian besar kompiler non-C99 akan bekerja sebaik yang mereka tawarkan inttypes.hdan stdbool.h; mis. gccDidukung keduanya jauh sebelum menawarkan dukungan penuh C99) .

Yang perlu dijalankan oleh program adalah setidaknya satu antarmuka di sistem Anda (selain antarmuka lokal) memiliki alamat IP yang ditetapkan dan rute default ditetapkan yang menggunakan antarmuka itu. Program akan mengumpulkan alamat IP itu dan menggunakannya sebagai "alamat spesifik" kedua.

Ini menguji semua kemungkinan kombinasi yang dapat Anda pikirkan:

  • Protokol TCP dan UDP
  • Soket normal, soket pendengar (server), soket multicast
  • SO_REUSEADDR atur pada socket1, socket2, atau kedua soket
  • SO_REUSEPORT atur pada socket1, socket2, atau kedua soket
  • Semua kombinasi alamat yang dapat Anda buat dari 0.0.0.0(wildcard), 127.0.0.1(alamat spesifik), dan alamat spesifik kedua yang ditemukan di antarmuka utama Anda (untuk multicast hanya 224.1.2.3di semua tes)

dan mencetak hasilnya dalam tabel yang bagus. Ini juga akan bekerja pada sistem yang tidak tahu SO_REUSEPORT, dalam hal ini opsi ini tidak diuji.

Apa yang tidak bisa dengan mudah diuji oleh program adalah bagaimana SO_REUSEADDRbertindak pada soket dalam TIME_WAITkeadaan seperti itu sangat sulit untuk memaksa dan menyimpan soket dalam keadaan itu. Untungnya sebagian besar sistem operasi tampaknya hanya berperilaku seperti BSD di sini dan sebagian besar programmer waktu dapat dengan mudah mengabaikan keberadaan negara itu.

Inilah kodenya (saya tidak bisa memasukkannya di sini, jawaban memiliki batas ukuran dan kode akan mendorong balasan ini melebihi batas).

Mecki
sumber
9
Misalnya, "alamat sumber" benar-benar harus "alamat lokal", tiga bidang selanjutnya juga. Mengikat dengan INADDR_ANYtidak mengikat alamat lokal yang ada, tetapi semua yang akan datang juga. listententu saja membuat soket dengan protokol, alamat lokal, dan port lokal yang sama persis, meskipun Anda mengatakan itu tidak mungkin.
Ben Voigt
9
@Ben Source dan Destination adalah istilah resmi yang digunakan untuk pengalamatan IP (yang saya rujuk utama). Lokal dan Remote tidak masuk akal, karena alamat Remote sebenarnya bisa menjadi alamat "Lokal" dan kebalikan dari Tujuan adalah Sumber dan bukan Lokal. Saya tidak tahu apa masalah Anda INADDR_ANY, saya tidak pernah mengatakan itu tidak akan mengikat ke alamat berikutnya. Dan listentidak membuat soket sama sekali, yang membuat seluruh kalimat Anda sedikit aneh.
Mecki
7
@Ben Ketika alamat baru ditambahkan ke sistem, itu juga merupakan "alamat lokal yang ada", itu baru mulai ada. Saya tidak mengatakan "ke semua alamat lokal yang ada saat ini ". Sebenarnya saya bahkan mengatakan bahwa soket sebenarnya benar-benar terikat dengan wildcard , yang berarti soket terikat dengan apa pun yang cocok dengan wildcard ini, sekarang, besok dan dalam seratus tahun. Mirip dengan sumber dan tujuan, Anda hanya melakukan nitpicking di sini. Apakah Anda memiliki kontribusi teknis nyata untuk dilakukan?
Mecki
8
@Mecki: Anda benar-benar berpikir bahwa kata yang ada mencakup hal-hal yang tidak ada sekarang tetapi akankah di masa depan? Sumber dan tujuan bukanlah nitpick. Ketika paket masuk dicocokkan dengan soket, Anda mengatakan bahwa alamat tujuan dalam paket akan dicocokkan dengan alamat "sumber" soket? Itu salah dan Anda tahu itu, Anda sudah mengatakan itu sumber dan tujuan adalah berlawanan. The lokal alamat pada soket dicocokkan dengan alamat tujuan paket yang masuk, dan ditempatkan di sumber alamat pada paket keluar.
Ben Voigt
10
@Mecki: Itu jauh lebih masuk akal jika Anda mengatakan "Alamat lokal soket adalah alamat sumber paket keluar dan alamat tujuan paket masuk". Paket memiliki alamat sumber dan tujuan. Host, dan soket pada host, jangan. Untuk soket datagram kedua rekannya sama. Untuk soket TCP, karena jabat tangan tiga arah, ada pencetus (klien) dan responden (server), tetapi itu masih tidak berarti koneksi atau soket yang terhubung memiliki sumber dan tujuan , karena lalu lintas mengalir dua arah.
Ben Voigt
1

Jawaban Mecki benar-benar sempurna, tetapi patut ditambahkan bahwa FreeBSD juga mendukung SO_REUSEPORT_LB, yang meniru SO_REUSEPORTperilaku Linux - ini menyeimbangkan beban; lihat setockopt (2)

Edward Tomasz Napierala
sumber
Temuan yang bagus. Saya tidak melihat itu di halaman manual ketika saya memeriksa. Layak disebutkan karena dapat sangat membantu ketika porting perangkat lunak Linux ke FreeBSD.
Mecki