Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
D
Dnsmasq
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nanahira
Dnsmasq
Commits
fa14bec8
Commit
fa14bec8
authored
Dec 20, 2015
by
Simon Kelley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Major tidy up of EDNS0 handling and computation/use of udp packet size.
parent
14a4ae88
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
168 additions
and
118 deletions
+168
-118
src/auth.c
src/auth.c
+7
-1
src/dnsmasq.h
src/dnsmasq.h
+5
-2
src/dnssec.c
src/dnssec.c
+0
-1
src/forward.c
src/forward.c
+132
-52
src/netlink.c
src/netlink.c
+2
-1
src/rfc1035.c
src/rfc1035.c
+21
-60
src/rrfilter.c
src/rrfilter.c
+1
-1
No files found.
src/auth.c
View file @
fa14bec8
...
@@ -81,7 +81,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut)
...
@@ -81,7 +81,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut)
}
}
size_t
answer_auth
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
time_t
now
,
union
mysockaddr
*
peer_addr
,
int
local_query
)
size_t
answer_auth
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
time_t
now
,
union
mysockaddr
*
peer_addr
,
int
local_query
,
int
do_bit
,
int
have_pseudoheader
)
{
{
char
*
name
=
daemon
->
namebuff
;
char
*
name
=
daemon
->
namebuff
;
unsigned
char
*
p
,
*
ansp
;
unsigned
char
*
p
,
*
ansp
;
...
@@ -820,6 +821,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
...
@@ -820,6 +821,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
header
->
ancount
=
htons
(
anscount
);
header
->
ancount
=
htons
(
anscount
);
header
->
nscount
=
htons
(
authcount
);
header
->
nscount
=
htons
(
authcount
);
header
->
arcount
=
htons
(
0
);
header
->
arcount
=
htons
(
0
);
/* Advertise our packet size limit in our reply */
if
(
have_pseudoheader
)
return
add_pseudoheader
(
header
,
ansp
-
(
unsigned
char
*
)
header
,
(
unsigned
char
*
)
limit
,
daemon
->
edns_pktsz
,
0
,
NULL
,
0
,
do_bit
);
return
ansp
-
(
unsigned
char
*
)
header
;
return
ansp
-
(
unsigned
char
*
)
header
;
}
}
...
...
src/dnsmasq.h
View file @
fa14bec8
...
@@ -1113,7 +1113,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
...
@@ -1113,7 +1113,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
int
no_cache
,
int
secure
,
int
*
doctored
);
int
no_cache
,
int
secure
,
int
*
doctored
);
size_t
answer_request
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
size_t
answer_request
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
struct
in_addr
local_addr
,
struct
in_addr
local_netmask
,
struct
in_addr
local_addr
,
struct
in_addr
local_netmask
,
time_t
now
,
int
*
ad_reqd
,
int
*
do_bit
);
time_t
now
,
int
ad_reqd
,
int
do_bit
,
int
have_pseudoheader
);
int
check_for_bogus_wildcard
(
struct
dns_header
*
header
,
size_t
qlen
,
char
*
name
,
int
check_for_bogus_wildcard
(
struct
dns_header
*
header
,
size_t
qlen
,
char
*
name
,
struct
bogus_addr
*
addr
,
time_t
now
);
struct
bogus_addr
*
addr
,
time_t
now
);
int
check_for_ignored_address
(
struct
dns_header
*
header
,
size_t
qlen
,
struct
bogus_addr
*
baddr
);
int
check_for_ignored_address
(
struct
dns_header
*
header
,
size_t
qlen
,
struct
bogus_addr
*
baddr
);
...
@@ -1123,6 +1123,8 @@ int check_for_local_domain(char *name, time_t now);
...
@@ -1123,6 +1123,8 @@ int check_for_local_domain(char *name, time_t now);
unsigned
int
questions_crc
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
buff
);
unsigned
int
questions_crc
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
buff
);
size_t
resize_packet
(
struct
dns_header
*
header
,
size_t
plen
,
size_t
resize_packet
(
struct
dns_header
*
header
,
size_t
plen
,
unsigned
char
*
pheader
,
size_t
hlen
);
unsigned
char
*
pheader
,
size_t
hlen
);
size_t
add_pseudoheader
(
struct
dns_header
*
header
,
size_t
plen
,
unsigned
char
*
limit
,
unsigned
short
udp_sz
,
int
optno
,
unsigned
char
*
opt
,
size_t
optlen
,
int
set_do
);
size_t
add_mac
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
limit
,
union
mysockaddr
*
l3
);
size_t
add_mac
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
limit
,
union
mysockaddr
*
l3
);
size_t
add_source_addr
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
limit
,
union
mysockaddr
*
source
);
size_t
add_source_addr
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
limit
,
union
mysockaddr
*
source
);
#ifdef HAVE_DNSSEC
#ifdef HAVE_DNSSEC
...
@@ -1141,7 +1143,8 @@ int private_net(struct in_addr addr, int ban_localhost);
...
@@ -1141,7 +1143,8 @@ int private_net(struct in_addr addr, int ban_localhost);
/* auth.c */
/* auth.c */
#ifdef HAVE_AUTH
#ifdef HAVE_AUTH
size_t
answer_auth
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
size_t
answer_auth
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
time_t
now
,
union
mysockaddr
*
peer_addr
,
int
local_query
);
time_t
now
,
union
mysockaddr
*
peer_addr
,
int
local_query
,
int
do_bit
,
int
have_pseudoheader
);
int
in_zone
(
struct
auth_zone
*
zone
,
char
*
name
,
char
**
cut
);
int
in_zone
(
struct
auth_zone
*
zone
,
char
*
name
,
char
**
cut
);
#endif
#endif
...
...
src/dnssec.c
View file @
fa14bec8
...
@@ -67,7 +67,6 @@ static char *algo_digest_name(int algo)
...
@@ -67,7 +67,6 @@ static char *algo_digest_name(int algo)
case
12
:
return
"gosthash94"
;
case
12
:
return
"gosthash94"
;
case
13
:
return
"sha256"
;
case
13
:
return
"sha256"
;
case
14
:
return
"sha384"
;
case
14
:
return
"sha384"
;
default:
return
NULL
;
default:
return
NULL
;
}
}
}
}
...
...
src/forward.c
View file @
fa14bec8
This diff is collapsed.
Click to expand it.
src/netlink.c
View file @
fa14bec8
...
@@ -288,7 +288,8 @@ int iface_enumerate(int family, void *parm, int (*callback)())
...
@@ -288,7 +288,8 @@ int iface_enumerate(int family, void *parm, int (*callback)())
rta
=
RTA_NEXT
(
rta
,
len1
);
rta
=
RTA_NEXT
(
rta
,
len1
);
}
}
if
(
inaddr
&&
mac
&&
callback_ok
)
if
(
!
(
neigh
->
ndm_state
&
(
NUD_NOARP
|
NUD_INCOMPLETE
|
NUD_FAILED
))
&&
inaddr
&&
mac
&&
callback_ok
)
if
(
!
((
*
callback
)(
neigh
->
ndm_family
,
inaddr
,
mac
,
maclen
,
parm
)))
if
(
!
((
*
callback
)(
neigh
->
ndm_family
,
inaddr
,
mac
,
maclen
,
parm
)))
callback_ok
=
0
;
callback_ok
=
0
;
}
}
...
...
src/rfc1035.c
View file @
fa14bec8
...
@@ -489,8 +489,8 @@ struct macparm {
...
@@ -489,8 +489,8 @@ struct macparm {
union
mysockaddr
*
l3
;
union
mysockaddr
*
l3
;
};
};
s
tatic
s
ize_t
add_pseudoheader
(
struct
dns_header
*
header
,
size_t
plen
,
unsigned
char
*
limit
,
size_t
add_pseudoheader
(
struct
dns_header
*
header
,
size_t
plen
,
unsigned
char
*
limit
,
int
optno
,
unsigned
char
*
opt
,
size_t
optlen
,
int
set_do
)
unsigned
short
udp_sz
,
int
optno
,
unsigned
char
*
opt
,
size_t
optlen
,
int
set_do
)
{
{
unsigned
char
*
lenp
,
*
datap
,
*
p
;
unsigned
char
*
lenp
,
*
datap
,
*
p
;
int
rdlen
,
is_sign
;
int
rdlen
,
is_sign
;
...
@@ -508,7 +508,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
...
@@ -508,7 +508,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return
plen
;
return
plen
;
*
p
++
=
0
;
/* empty name */
*
p
++
=
0
;
/* empty name */
PUTSHORT
(
T_OPT
,
p
);
PUTSHORT
(
T_OPT
,
p
);
PUTSHORT
(
SAFE_PKTSZ
,
p
);
/* max packet length, this will be overwritten
*/
PUTSHORT
(
udp_sz
,
p
);
/* max packet length, 512 if not given in EDNS0 header
*/
PUTSHORT
(
0
,
p
);
/* extended RCODE and version */
PUTSHORT
(
0
,
p
);
/* extended RCODE and version */
PUTSHORT
(
set_do
?
0x8000
:
0
,
p
);
/* DO flag */
PUTSHORT
(
set_do
?
0x8000
:
0
,
p
);
/* DO flag */
lenp
=
p
;
lenp
=
p
;
...
@@ -594,7 +594,7 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
...
@@ -594,7 +594,7 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
if
(
!
match
)
if
(
!
match
)
return
1
;
/* continue */
return
1
;
/* continue */
parm
->
plen
=
add_pseudoheader
(
parm
->
header
,
parm
->
plen
,
parm
->
limit
,
EDNS0_OPTION_MAC
,
(
unsigned
char
*
)
mac
,
maclen
,
0
);
parm
->
plen
=
add_pseudoheader
(
parm
->
header
,
parm
->
plen
,
parm
->
limit
,
PACKETSZ
,
EDNS0_OPTION_MAC
,
(
unsigned
char
*
)
mac
,
maclen
,
0
);
return
0
;
/* done */
return
0
;
/* done */
}
}
...
@@ -603,12 +603,6 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock
...
@@ -603,12 +603,6 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock
{
{
struct
macparm
parm
;
struct
macparm
parm
;
/* Must have an existing pseudoheader as the only ar-record,
or have no ar-records. Must also not be signed */
if
(
ntohs
(
header
->
arcount
)
>
1
)
return
plen
;
parm
.
header
=
header
;
parm
.
header
=
header
;
parm
.
limit
=
(
unsigned
char
*
)
limit
;
parm
.
limit
=
(
unsigned
char
*
)
limit
;
parm
.
plen
=
plen
;
parm
.
plen
=
plen
;
...
@@ -699,13 +693,13 @@ size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, unio
...
@@ -699,13 +693,13 @@ size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, unio
struct
subnet_opt
opt
;
struct
subnet_opt
opt
;
len
=
calc_subnet_opt
(
&
opt
,
source
);
len
=
calc_subnet_opt
(
&
opt
,
source
);
return
add_pseudoheader
(
header
,
plen
,
(
unsigned
char
*
)
limit
,
EDNS0_OPTION_CLIENT_SUBNET
,
(
unsigned
char
*
)
&
opt
,
len
,
0
);
return
add_pseudoheader
(
header
,
plen
,
(
unsigned
char
*
)
limit
,
PACKETSZ
,
EDNS0_OPTION_CLIENT_SUBNET
,
(
unsigned
char
*
)
&
opt
,
len
,
0
);
}
}
#ifdef HAVE_DNSSEC
#ifdef HAVE_DNSSEC
size_t
add_do_bit
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
limit
)
size_t
add_do_bit
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
limit
)
{
{
return
add_pseudoheader
(
header
,
plen
,
(
unsigned
char
*
)
limit
,
0
,
NULL
,
0
,
1
);
return
add_pseudoheader
(
header
,
plen
,
(
unsigned
char
*
)
limit
,
PACKETSZ
,
0
,
NULL
,
0
,
1
);
}
}
#endif
#endif
...
@@ -1525,16 +1519,16 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
...
@@ -1525,16 +1519,16 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
/* return zero if we can't answer from cache, or packet size if we can */
/* return zero if we can't answer from cache, or packet size if we can */
size_t
answer_request
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
size_t
answer_request
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
struct
in_addr
local_addr
,
struct
in_addr
local_netmask
,
struct
in_addr
local_addr
,
struct
in_addr
local_netmask
,
time_t
now
,
int
*
ad_reqd
,
int
*
do_bit
)
time_t
now
,
int
ad_reqd
,
int
do_bit
,
int
have_pseudoheader
)
{
{
char
*
name
=
daemon
->
namebuff
;
char
*
name
=
daemon
->
namebuff
;
unsigned
char
*
p
,
*
ansp
,
*
pheader
;
unsigned
char
*
p
,
*
ansp
;
unsigned
int
qtype
,
qclass
;
unsigned
int
qtype
,
qclass
;
struct
all_addr
addr
;
struct
all_addr
addr
;
int
nameoffset
;
int
nameoffset
;
unsigned
short
flag
;
unsigned
short
flag
;
int
q
,
ans
,
anscount
=
0
,
addncount
=
0
;
int
q
,
ans
,
anscount
=
0
,
addncount
=
0
;
int
dryrun
=
0
,
sec_reqd
=
0
,
have_pseudoheader
=
0
;
int
dryrun
=
0
;
struct
crec
*
crecp
;
struct
crec
*
crecp
;
int
nxdomain
=
0
,
auth
=
1
,
trunc
=
0
,
sec_data
=
1
;
int
nxdomain
=
0
,
auth
=
1
,
trunc
=
0
,
sec_data
=
1
;
struct
mx_srv_record
*
rec
;
struct
mx_srv_record
*
rec
;
...
@@ -1550,35 +1544,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
...
@@ -1550,35 +1544,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if
(
header
->
hb4
&
HB4_CD
)
if
(
header
->
hb4
&
HB4_CD
)
sec_data
=
0
;
sec_data
=
0
;
/* RFC 6840 5.7 */
*
ad_reqd
=
header
->
hb4
&
HB4_AD
;
*
do_bit
=
0
;
/* If there is an additional data section then it will be overwritten by
/* If there is an additional data section then it will be overwritten by
partial replies, so we have to do a dry run to see if we can answer
partial replies, so we have to do a dry run to see if we can answer
the query. */
the query. */
if
(
ntohs
(
header
->
arcount
)
!=
0
)
if
(
ntohs
(
header
->
arcount
)
!=
0
)
{
dryrun
=
1
;
dryrun
=
1
;
/* If there's an additional section, there might be an EDNS(0) pseudoheader */
if
(
find_pseudoheader
(
header
,
qlen
,
NULL
,
&
pheader
,
NULL
))
{
unsigned
short
flags
;
have_pseudoheader
=
1
;
pheader
+=
4
;
/* udp size, ext_rcode */
GETSHORT
(
flags
,
pheader
);
if
((
sec_reqd
=
flags
&
0x8000
))
{
*
do_bit
=
1
;
/* do bit */
*
ad_reqd
=
1
;
}
}
}
for
(
rec
=
daemon
->
mxnames
;
rec
;
rec
=
rec
->
next
)
for
(
rec
=
daemon
->
mxnames
;
rec
;
rec
=
rec
->
next
)
rec
->
offset
=
0
;
rec
->
offset
=
0
;
...
@@ -1603,11 +1573,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
...
@@ -1603,11 +1573,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
GETSHORT
(
qtype
,
p
);
GETSHORT
(
qtype
,
p
);
GETSHORT
(
qclass
,
p
);
GETSHORT
(
qclass
,
p
);
/* Don't filter RRSIGS from answers to ANY queries, even if do-bit
not set. */
if
(
qtype
==
T_ANY
)
*
do_bit
=
1
;
ans
=
0
;
/* have we answered this question */
ans
=
0
;
/* have we answered this question */
if
(
qtype
==
T_TXT
||
qtype
==
T_ANY
)
if
(
qtype
==
T_TXT
||
qtype
==
T_ANY
)
...
@@ -1739,7 +1704,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
...
@@ -1739,7 +1704,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
the zone is unsigned, which implies that we're doing
the zone is unsigned, which implies that we're doing
validation. */
validation. */
if
((
crecp
->
flags
&
(
F_HOSTS
|
F_DHCP
|
F_CONFIG
))
||
if
((
crecp
->
flags
&
(
F_HOSTS
|
F_DHCP
|
F_CONFIG
))
||
!
sec_reqd
||
!
do_bit
||
(
option_bool
(
OPT_DNSSEC_VALID
)
&&
!
(
crecp
->
flags
&
F_DNSSECOK
)))
(
option_bool
(
OPT_DNSSEC_VALID
)
&&
!
(
crecp
->
flags
&
F_DNSSECOK
)))
{
{
do
do
...
@@ -1927,7 +1892,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
...
@@ -1927,7 +1892,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
}
}
/* If the client asked for DNSSEC don't use cached data. */
/* If the client asked for DNSSEC don't use cached data. */
if
((
crecp
->
flags
&
(
F_HOSTS
|
F_DHCP
|
F_CONFIG
))
||
!
sec_reqd
||
!
(
crecp
->
flags
&
F_DNSSECOK
))
if
((
crecp
->
flags
&
(
F_HOSTS
|
F_DHCP
|
F_CONFIG
))
||
!
do_bit
||
!
(
crecp
->
flags
&
F_DNSSECOK
))
do
do
{
{
/* don't answer wildcard queries with data not from /etc/hosts
/* don't answer wildcard queries with data not from /etc/hosts
...
@@ -1961,17 +1926,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
...
@@ -1961,17 +1926,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if
(
crecp
->
flags
&
F_NEG
)
if
(
crecp
->
flags
&
F_NEG
)
{
{
/* We don't cache NSEC records, so if a DNSSEC-validated negative answer
ans
=
1
;
is cached and the client wants DNSSEC, forward rather than answering from the cache */
auth
=
0
;
if
(
!
sec_reqd
||
!
(
crecp
->
flags
&
F_DNSSECOK
))
if
(
crecp
->
flags
&
F_NXDOMAIN
)
{
nxdomain
=
1
;
ans
=
1
;
if
(
!
dryrun
)
auth
=
0
;
log_query
(
crecp
->
flags
,
name
,
NULL
,
NULL
);
if
(
crecp
->
flags
&
F_NXDOMAIN
)
nxdomain
=
1
;
if
(
!
dryrun
)
log_query
(
crecp
->
flags
,
name
,
NULL
,
NULL
);
}
}
}
else
else
{
{
...
@@ -2209,10 +2169,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
...
@@ -2209,10 +2169,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
len
=
ansp
-
(
unsigned
char
*
)
header
;
len
=
ansp
-
(
unsigned
char
*
)
header
;
/* Advertise our packet size limit in our reply */
if
(
have_pseudoheader
)
if
(
have_pseudoheader
)
len
=
add_pseudoheader
(
header
,
len
,
(
unsigned
char
*
)
limit
,
0
,
NULL
,
0
,
sec_reqd
);
len
=
add_pseudoheader
(
header
,
len
,
(
unsigned
char
*
)
limit
,
daemon
->
edns_pktsz
,
0
,
NULL
,
0
,
do_bit
);
if
(
*
ad_reqd
&&
sec_data
)
if
(
ad_reqd
&&
sec_data
)
header
->
hb4
|=
HB4_AD
;
header
->
hb4
|=
HB4_AD
;
else
else
header
->
hb4
&=
~
HB4_AD
;
header
->
hb4
&=
~
HB4_AD
;
...
...
src/rrfilter.c
View file @
fa14bec8
...
@@ -243,7 +243,7 @@ size_t rrfilter(struct dns_header *header, size_t plen, int mode)
...
@@ -243,7 +243,7 @@ size_t rrfilter(struct dns_header *header, size_t plen, int mode)
for
(
p
=
rrs
[
0
],
i
=
1
;
i
<
rr_found
;
i
+=
2
)
for
(
p
=
rrs
[
0
],
i
=
1
;
i
<
rr_found
;
i
+=
2
)
{
{
unsigned
char
*
start
=
rrs
[
i
];
unsigned
char
*
start
=
rrs
[
i
];
unsigned
char
*
end
=
(
i
!=
rr_found
-
1
)
?
rrs
[
i
+
1
]
:
((
unsigned
char
*
)
(
header
+
1
)
)
+
plen
;
unsigned
char
*
end
=
(
i
!=
rr_found
-
1
)
?
rrs
[
i
+
1
]
:
((
unsigned
char
*
)
header
)
+
plen
;
memmove
(
p
,
start
,
end
-
start
);
memmove
(
p
,
start
,
end
-
start
);
p
+=
end
-
start
;
p
+=
end
-
start
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment