-
Notifications
You must be signed in to change notification settings - Fork 11
/
multicast_server.rb
97 lines (82 loc) · 2.15 KB
/
multicast_server.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
require 'socket'
require 'ipaddr'
require 'thread'
class String
def colorize(color_code)
"\e[#{color_code}m#{self}\e[0m"
end
def red
colorize(91)
end
def green
colorize(92)
end
def yellow
colorize(93)
end
def cyan
colorize(96)
end
end
class Server
CAST_ADDR = "224.0.0.1"
BIND = "0.0.0.0"
PORT = 30512
attr_reader :ip_addr
def initialize(ip)
@ip_addr = ip
end
def listen
socket.bind(BIND, PORT)
puts "Startup! Replying with IP #{ip_addr}".green
loop do
begin
data, clientAddr = socket.recvfrom(150)
Thread.start(data,clientAddr) do |data,clientAddr|
# We don't want to reply to ourselves
if data[0..3] != "ack," and data.length == 32
puts "New message from #{clientAddr[3]}".cyan
message = Response.new(data, ip_addr)
sleep 0.1 # Give the client a few ms to start listening
puts "Responding".yellow
socket.send(message.to_csv, 0, CAST_ADDR, PORT)
end
end
rescue Errno::EMSGSIZE,Errno::ENOBUFS => e
puts "Error with recieved packet: #{e.message}".red
end
end
end
private
def socket
@socket ||= UDPSocket.open.tap do |socket|
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, bind_hton)
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 1)
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
end
end
def bind_hton
IPAddr.new(CAST_ADDR).hton + IPAddr.new(BIND).hton
end
end
class Response
#
# Responses are CSV formatted plaintext. They are multicasted back and
# are structured as so: "ack,[User Unique ID],[Twitarr Address]"
#
attr_reader :uuid, :address
def initialize(uuid, ip)
@uuid = uuid
@address = ip
end
def to_a
["ack", uuid, address]
end
def to_csv
self.to_a.join(",")
end
end
# This is messy and sucks.
# The IP the server will reply with for twitarr, we can enter anything on this. Even a DNS uri! It's just a string.
ip = ARGV[0] || Socket.ip_address_list.detect{|intf| intf.ipv4_private?}.ip_address # Woo, stackoverflow!
Server.new(ip).listen